七天接手react項目 系列 —— react 腳手架創建項目


其他章節請看:

七天接手react項目 系列

react 腳手架創建項目

前面我們一直通過 script 的方式學習 react 基礎知識,而真實項目通常是基於腳手架進行開發。

本篇首先通過 react 腳手架創建項目,分析其目錄結構,接着編寫第一個組件、解決樣式覆蓋,最后配置代理 proxy 以及通過消息發布與訂閱解決兄弟組件之間的通信問題。

Tip:我們要接手的 react 項目是:spug_web

使用 react 腳手架創建項目 react-cli-demo

前面我們學習 vue 腳手架 vue-cli 創建一個項目是這樣:

> vue create vue-hello-world

在 react 中創建項目是這樣:

$ npx create-react-app react-cli-demo

Creating a new React app in exercise\react-cli-demo.

Installing packages. This might take a couple of minutes.
Installing react, react-dom, and react-scripts with cra-template...


added 1368 packages in 2m

169 packages are looking for funding
  run `npm fund` for details

Initialized a git repository.

Installing template dependencies using npm...
npm WARN deprecated source-map-resolve@0.6.0: See https://github.com/lydell/source-map-resolve#deprecated

added 38 packages in 9s

169 packages are looking for funding
  run `npm fund` for details
Removing template package using npm...


removed 1 package, and audited 1406 packages in 4s

169 packages are looking for funding
  run `npm fund` for details

6 moderate severity vulnerabilities

To address all issues (including breaking changes), run:
  npm audit fix --force

Run `npm audit` for details.

Created git commit.

Success! Created react-cli-demo at exercise\react-cli-demo
Inside that directory, you can run several commands:

  npm start
    Starts the development server.

  npm run build
    Bundles the app into static files for production.

  npm test
    Starts the test runner.

  npm run eject
    Removes this tool and copies build dependencies, configuration files
    and scripts into the app directory. If you do this, you can't go back!

We suggest that you begin by typing:

  cd react-cli-demo
  npm start

Happy hacking!

Create React App 是一個用於學習 React 的舒適環境,也是用 React 創建新的單頁應用最佳方式 —— 官網-Create React App

$ cd react-cli-demo/

本地啟動項目:

$ npm start

> react-cli-demo@0.1.0 start
> react-scripts start

(node:3880) [DEP_WEBPACK_DEV_SERVER_ON_AFTER_SETUP_MIDDLEWARE] DeprecationWarning: 'onAfterSetupMiddleware' option is deprecated. Please use the 'setupMiddlewares' option.
(Use `node --trace-deprecation ...` to show where the warning was created)
(node:3880) [DEP_WEBPACK_DEV_SERVER_ON_BEFORE_SETUP_MIDDLEWARE] DeprecationWarning: 'onBeforeSetupMiddleware' option is deprecated. Please use the 'setupMiddlewares' option.
Starting the development server...

Compiled successfully!

You can now view react-cli-demo in the browser.

  Local:            http://localhost:3000
  On Your Network:  http://192.168.85.1:3000

Note that the development build is not optimized.
To create a production build, use npm run build.

assets by path static/ 1.49 MiB
  asset static/js/bundle.js 1.48 MiB [emitted] (name: main) 1 related asset
  asset static/js/node_modules_web-vitals_dist_web-vitals_js.chunk.js 6.93 KiB [emitted] 1 related asset
  asset static/media/logo.6ce24c58023cc2f8fd88fe9d219db6c6.svg 2.57 KiB [emitted] (auxiliary name: main)
asset index.html 1.67 KiB [emitted]
asset asset-manifest.json 546 bytes [emitted]
cached modules 1.37 MiB (javascript) 31.3 KiB (runtime) [cached] 122 modules
webpack 5.69.1 compiled successfully in 1867 ms

自動打開網頁:
react-cli-demo-1

react-cli-demo 目錄結構分析

exercise\react-cli-demo> dir

Mode                 LastWriteTime         Length Name
----                 -------------         ------ ----
d-----          2022/3/3     17:50                node_modules
d-----          2022/3/3     17:48                public
d-----         2022/3/18     19:22                src
-a----        1985/10/26     16:15            310 .gitignore
-a----          2022/3/3     17:49        1120931 package-lock.json
-a----          2022/3/3     17:49            817 package.json
-a----        1985/10/26     16:15           3359 README.md

一級目錄結構很簡單,我們主要分析一下 publicsrc 目錄

public 目錄

public> dir

Mode                 LastWriteTime         Length Name
----                 -------------         ------ ----
-a----        1985/10/26     16:15           3870 favicon.ico
-a----          2022/3/4     16:06           1966 index.html
-a----        1985/10/26     16:15           5347 logo192.png
-a----        1985/10/26     16:15           9664 logo512.png
-a----        1985/10/26     16:15            492 manifest.json
-a----        1985/10/26     16:15             67 robots.txt

從中我們猜測主要文件應該是 index.html。內容如下:

// 注釋已全部刪除

<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="utf-8" />
  <link rel="icon" href="%PUBLIC_URL%/favicon.ico" />
  <meta name="viewport" content="width=device-width, initial-scale=1" />
  <meta name="theme-color" content="#000000" />
  <meta name="description" content="Web site created using create-react-app" />
  <link rel="apple-touch-icon" href="%PUBLIC_URL%/logo192.png" />
  <link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
  <title>React App</title>
</head>

<body>
  <noscript>You need to enable JavaScript to run this app.</noscript>
  <div id="root"></div>
</body>

</html>

index.html核心就是 <div id="root"></div>,即掛載的根元素

Tiprobots.txtrobots協議,只是約定俗成的,所以並不能保證網站的隱私

src 目錄

src> dir

Mode                 LastWriteTime         Length Name
----                 -------------         ------ ----
d-----          2022/3/4     16:56                components
-a----        1985/10/26     16:15            564 App.css
-a----         2022/3/18     19:22            553 App.js
-a----        1985/10/26     16:15            246 App.test.js
-a----        1985/10/26     16:15            366 index.css
-a----        1985/10/26     16:15            500 index.js
-a----        1985/10/26     16:15           2632 logo.svg
-a----        1985/10/26     16:15            362 reportWebVitals.js
-a----        1985/10/26     16:15            241 setupTests.js
index.js 和 App.js

哪個是入口文件?App.js 還是 index.js?我們先看一下這兩個文件的內容:

// index.js 已刪除注釋

import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './App';
import reportWebVitals from './reportWebVitals';

ReactDOM.render(
  <React.StrictMode>
    <App />
  </React.StrictMode>,
  document.getElementById('root')
);

reportWebVitals();
// App.js
import logo from './logo.svg';
import './App.css';

function App() {
  return (
    <div className="App">
      <header className="App-header">
        <img src={logo} className="App-logo" alt="logo" />
        <p>
          Edit <code>src/App.js</code> and save to reload.
        </p>
        <a
          className="App-link"
          href="https://reactjs.org"
          target="_blank"
          rel="noopener noreferrer"
        >
          Learn React
        </a>
      </header>
    </div>
  );
}

export default App;

index.js 引用了 App.js

我們在回憶一下 vue-cli 生成的項目,也有 App.js 文件,不過是被 main.js 引用。內容如下:

// main.js
import Vue from 'vue'
import App from './App.vue'
import router from './router'
import store from './store'

Vue.config.productionTip = false

new Vue({
  router,
  store,
  render: h => h(App)
}).$mount('#app')
// App.js
<template>
  <div id="app">
    <div id="nav">
      <router-link to="/">Home</router-link> |
      <router-link to="/about">About</router-link>
    </div>
    <router-view/>
  </div>
</template>

對比發現,都是將 App 組件掛載到 dom 上 —— react 是 #root,vue 是 #app

至此,我們知道 index.js入口文件,而 App 應該是根組件。

App.css

上面我們啟動 react-cli-demo 項目,網頁中有這么一句話:

Edit src/App.js and save to reload.

編輯 src/App.js 並保存以重新加載。

App.js 中有 import './App.css';,筆者嘗試修改一下 App.css

.App-header {
- background-color: #282c34;
+ background-color: orange;
  ...
}

保存后,發現頁面背景自動變成橙色

於是我們知道 App.css 應該是 App 組件的樣式。而 vue 中樣式、html和 js 都在一個 .vue 文件中。

第一個組件 HelloWorld

組件創建有兩種方式:函數組件以及類組件。

這里的 App.js 使用的是函數組件,我們也用函數的方式創建組件,然后讓 App 加載。請看示例:

// App.js
// webpack 中就可以省略 .js
import HelloWorld from './HelloWorld.js';

export default function App() {
  return (
    <div className="App">
      < HelloWorld />
    </div >
  );
}
// HelloWorld.js
export default function HelloWorld() {
    return <div>hello world!</div>
}

頁面顯示 hello world!

對應類組件的實現:

import { Component } from 'react'
export default class HelloWorld extends Component {
    render() {
        return <div>hello world!</div>
    }
}

后綴js/jsx

我們寫的組件 HelloWorld 其實是 jsx 語法,所以可以將 HelloWorld.js 改為 HelloWorld.jsx,引入的后綴名也同步一下即可。

import HelloWorld from './HelloWorld.jsx';

Tip:純邏輯的 js 可以用 .js 或小寫(例如 http.js),組件用 Http.jsHttp.jsx

Create React App 在內部使用 webpack,而 webpack 能夠使用戶在引入模塊時不帶擴展:

import File from '../path/to/file';

經測試,無論是 HelloWorld.js 還是 HelloWorld.jsx,都可以省略擴展名引入:

import HelloWorld from './HelloWorld';

components

src/App.js 作為組件的根組件(或組件殼子),現在我們的HelloWorld 組件和它是同一目錄,倘若以后組件變多了,豈不是不好管理,所以我們可以將組件放在 src/components 文件夾中。請看實現:

將 HelloWorld 組件代碼移至 index.js

// src/components/HelloWorld/index.js

export default function HelloWorld() {
    return <div>hello world!</div>
}

App.js 中修改引入組件的代碼:

// 默認會去加載 HelloWorld 文件夾中的 index.js或 index.jsx
import HelloWorld from './components/HelloWorld';

...

樣式覆蓋

假如我在 App 中引入兩個組件,每個組件有自己的樣式,讓若發生沖突怎么辦?請看示例:

// App.js
import HelloWorld from './components/HelloWorld'
import HelloWorld2 from './components/HelloWorld2'

export default function App() {
  return (
    <div className="App">
      < HelloWorld />
      < HelloWorld2 />
    </div >
  );
}

組件1 的文字是藍色

import './index.css'

export default function HelloWorld() {
    return <div className="title">hello world!</div>
}
.title{color:blue}

組件2與組件1相同,唯一區別是文字顏色為紅色

.title{color:red}

最終,頁面中兩個組件的文字顏色都是紅色

頁面有如下代碼:

<style>.title{color:blue}
</style>
<style>.title{color:red}
</style>

<div class="title">hello world!</div>
<div class="title">hello world!</div>

由此我們知道后者樣式將前者給覆蓋了。

樣式模塊化

我們可以使用樣式模塊化來修復樣式覆蓋的問題。

比如我要將 HelloWorld 組件樣式模塊化,只需要兩步:

首先重命名樣式文件。在名字和 css 之間增加 module

component/HelloWorl/index.css

// 重命名后
component/HelloWorl/index.module.css

然后使用樣式的方式也得調整。就像這樣:

import helloWorld from './index.module.css'

export default function HelloWorld() {
    return <div className={helloWorld.title}>hello world!</div>
}

最終,頁面中兩個組件的文字顏色分別是藍色紅色,與預期相符。

頁面有如下代碼:

<style>.HelloWorld_title__kRYA7{color:blue}
</style>
<style>.title{color:red}
</style>

<div class="HelloWorld_title__kRYA7">hello world!</div>
<div class="title">hello world!</div>

Tip:效果其實和 vue 中 Scoped Css 類似

less

我們還可以使用 less 這類 css 預處理語言來避免樣式沖突。就像這樣:

.HelloWorld {
  .title{color: blue}
}
.HelloWorld2 {
  .title{color: red}
}

代理 Proxy

在 vue-cli 中我們曾使用 proxy 做過一個需求:新建一個頁面,里面有 2 個按鈕,點擊按鈕能發出相應的請求,一個是非跨域請求,一個是跨域請求。

這里我們也實現一下。無需按鈕,之間在組件中發請求即可。

在 React 開發中,你能使用任何你喜歡的 AJAX 庫,比如社區比較流行的 Axios,jQuery AJAX,或者是瀏覽器內置的 window.fetch —— 官網-如何在 React 中發起 AJAX 請求?

Tip:React 和 Vue 將注意力集中保持在核心庫,而將其他功能如ajax、路由和全局狀態管理交給相關的庫。

axios

我們曾在 vue-loader 擴展 這里將 axios 集成到項目中。這里卻無需那么復雜,只需能發出 ajax 請求。

根據 axios 官網 介紹,簡單使用只需兩步:

首先下載依賴包:

react-cli-demo> npm i axios

added 1 package, and audited 1407 packages in 7s

169 packages are looking for funding
  run `npm fund` for details

6 moderate severity vulnerabilities

To address all issues (including breaking changes), run:
  npm audit fix --force

Run `npm audit` for details.

在 HelloWorld 組件中通過 axios 發起一個 ajax 請求:

// src/components/HelloWorld/index.jsx

const axios = require('axios');

export default function HelloWorld() {
    axios.get('/index.html')
        .then(function (response) {
            console.log(response.data);
        })

    return <div>hello world!</div>
}

瀏覽器控制台輸出:

<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="utf-8" />
  <link rel="icon" href="/favicon.ico" />
  <meta name="viewport" content="width=device-width, initial-scale=1" />
  <meta name="theme-color" content="#000000" />
  <meta name="description" content="Web site created using create-react-app" />
  <link rel="apple-touch-icon" href="/logo192.png" />
  <link rel="manifest" href="/manifest.json" />
  <title>React App</title>
<script defer src="/static/js/bundle.js"></script></head>

<body>
  <noscript>You need to enable JavaScript to run this app.</noscript>
  <div id="root"></div>
</body>

</html>

請求將 react-cli-demo/public/index.html 的內容返了回來,於是我們知道本地服務器的根是 public 目錄。

setupProxy.js

spug_web 中有個叫 setupProxy.js 的文件,內容如下:

// src/setupProxy.js
const proxy = require('http-proxy-middleware');

module.exports = function (app) {
  app.use(proxy('/api/', {
    target: 'http://127.0.0.1:8000',
    changeOrigin: true,
    ws: true,
    headers: {'X-Real-IP': '1.1.1.1'},
    pathRewrite: {
      '^/api': ''
    }
  }))
};

Tiphttp-proxy-middleware - http 代理中間件。

對比在 vue 中做 proxy 代理,代碼非常相似:

// vue.config.js
module.exports = {
  devServer: {
    proxy: {
      // 只有 /pengjiali 的請求會被代理
      '/pengjiali': {
        target: 'https://www.cnblogs.com/',
        // changeOrigin: true
      },
    }
  }
}

接下來我們就依葫蘆畫瓢:

新建 setupProxy.js

// src/setupProxy.js

const { createProxyMiddleware } = require('http-proxy-middleware');

module.exports = function (app) {
  app.use(
    // 將原來的 proxy 改為 createProxyMiddleware 
    createProxyMiddleware(
      '/pengjiali',
      {
        target: 'https://www.cnblogs.com/',
        changeOrigin: true
      }
    )
  )
}
// src/components/HelloWorld/index.jsx

const axios = require('axios');

export default function HelloWorld() {
    axios.get('/pengjiali/p/14561119.html')
        .then(function (response) {
            // handle success
            console.log(response.data);
        }).catch(function (error) {
            // handle error
            console.log(error);
        })

    return <div>hello world2!</div>
}

重啟服務,控制台輸出博文內容。

http-proxy-middleware 有兩點和 spug_web 不同:

  • 筆者這版的react腳手架默認已有 http-proxy-middleware,所以我們無需在下載。
    • vscode 中將鼠標移至 http-proxy-middleware 就會顯示該文件路徑。
  • 用法上從 proxy 改為 createProxyMiddleware
    • 我們的版本是 2.0.4,也是此刻官網最新版,其用法使用的就是 createProxyMiddleware
// 本地版本
react-cli-demo/node_modules/http-proxy-middleware (master)
$ cat package.json |head -n 5
{
  "name": "http-proxy-middleware",
  "version": "2.0.4",
  "description": "The one-liner node.js proxy middleware for connect, express and browser-sync",
  "main": "dist/index.js",

消息訂閱與發布

在 vue 中我們可以使用中央事件總線(或稱 bus)來解決兄弟組件之間的通信。bus 相當於一個中介,組件可以在其上訂閱消息,當觸發時就會將消息通知到訂閱者。其原理其實就是消息訂閱與發布。

react 可以通過 pubsub-js 包來實現組件之間通信。

Tip:PubSubJS 是一個用 JavaScript 編寫的基於主題的發布/訂閱庫 —— pubsub-js

下面我們定義兩個組件,組件1訂閱消息,組件2發布消息。請看實現:

首先按照依賴包:

react-cli-demo> npm i pubsub-js

added 1 package, and audited 1408 packages in 5s

169 packages are looking for funding
  run `npm fund` for details

6 moderate severity vulnerabilities

To address all issues (including breaking changes), run:
  npm audit fix --force

Run `npm audit` for details.

接着定義兩個組件:

// 組件1訂閱消息
// src/components/HelloWorld/index.jsx

import PubSub from 'pubsub-js'

export default function HelloWorld() {
    // 訂閱 message1
    PubSub.subscribe('msg1', function (msg, data) {
        console.log(msg, data);
    });

    return <div>hello world!</div>
}
// 組件2發布消息
// src/components/HelloWorld2/index.jsx

import PubSub from 'pubsub-js'

export default function HelloWorld() {
    PubSub.publish('msg1', '旅游去');
    return <div>hello world2!</div>
}

頁面控制台顯示:msg1 旅游去

其他章節請看:

七天接手react項目 系列


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM