<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          寫給前端新人:從 0到1 搭建一個(gè)前端項(xiàng)目,都需要做什么?

          共 23055字,需瀏覽 47分鐘

           ·

          2021-05-12 21:39

          點(diǎn)擊上方關(guān)注 前端技術(shù)江湖,一起學(xué)習(xí),天天進(jìn)步


          不使用腳手架搭建 React 項(xiàng)目:https://github.com/zhuyuanmin/react-0-1-build。讀者可根據(jù)提交的分支順序一步步搭建,所以庫都使用了最新版本,讓我們?cè)诓瓤又谐砷L!【master 分支:完整版,不包含 typescript ;typescript-react 分支: 包含 typescript 的完整版本】

          一、項(xiàng)目啟動(dòng)

          1. 了解需求背景

          2. 了解業(yè)務(wù)流程

          二、項(xiàng)目搭建初始化

          本案例使用腳手架 create-react-app 初始化了項(xiàng)目。此腳手架有利有弊吧,項(xiàng)目目錄結(jié)構(gòu)簡潔,不需要太關(guān)心 webpack 令人頭疼的配置;弊端在于,腳手架確實(shí)有些龐大,構(gòu)建時(shí)間在 4mins 左右。各位看官擇優(yōu)選擇吧,也可以完全自己搭建一個(gè)項(xiàng)目。

          1. 設(shè)置淘寶鏡像倉庫

          $ yarn config set registry registry.npm.taobao.org/ -g

          $ yarn config set sass_binary_site cdn.npm.taobao.org/dist/node-sass -g

          1. 工程目錄 init

          $ create-react-app qpj-web-pc --typescript

          $ tree -I "node_modules"

          .
          |-- README.md
          |-- package.json
          |-- public
          |   |-- favicon.ico   
          |   |-- index.html
          |   |-- logo192.png   
          |   |-- logo512.png   
          |   |-- manifest.json 
          |   `-- robots.txt
          |-- src
          |   |-- App.css
          |   |-- App.test.tsx  
          |   |-- App.tsx
          |   |-- index.css
          |   |-- index.tsx
          |   |-- logo.svg
          |   |-- react-app-env.d.ts
          |   |-- reportWebVitals.ts
          |   `-- setupTests.ts 
          `-- tsconfig.json
          1. yarn build 試試

          $ yarn build & tree -I "node_modules"

          .
          |-- README.md
          |-- build/ # 改造點(diǎn)(由于 `Jenkins` 構(gòu)建打包腳本有可能已經(jīng)寫死了 `dist` 包名)
          |-- package.json
          |-- public
          |   |-- favicon.ico   
          |   |-- index.html
          |   |-- logo192.png   
          |   |-- logo512.png   
          |   |-- manifest.json 
          |   `-- robots.txt
          |-- src
          |   |-- App.css
          |   |-- App.test.tsx  
          |   |-- App.tsx
          |   |-- index.css
          |   |-- index.tsx
          |   |-- logo.svg
          |   |-- react-app-env.d.ts
          |   |-- reportWebVitals.ts
          |   `-- setupTests.ts 
          `-- tsconfig.json
          1. 連接 git 遠(yuǎn)程倉庫

          $ git remote add origin yuanmin.zhu%40wetax.com.cn:[email protected]/front/qpj-web-pc.git

          1. 添加 .gitignore

          $ echo -e " yarn.lock \n package-lock.json \n /dist \n .idea" >> .gitignore

          1. 添加 eslint 代碼及提交評(píng)論校驗(yàn)

          $ yarn add husky lint-staged @commitlint/cli @commitlint/config-conventional -D

          $ npx husky install

          $ npx husky add .husky/pre-commit "npx lint-staged"

          $ npx husky add .husky/prepare-commit-msg "npx commitlint -e"

          • 項(xiàng)目根目錄新建 commitlint.config.js
          // commitlint.config.js
          module.exports = {
            extends: ['@commitlint/config-conventional'],
            rules: {
                'type-enum': [
                    2,
                    'always',
                    ['feat''fix''docs''style''refactor''test''chore''revert'],
                ],
                'subject-full-stop': [0'never'],
                'subject-case': [0'never'],
            },
          }
          • vscode 擴(kuò)展中搜索 ESLint 并安裝,項(xiàng)目根目錄新建 .eslintrc.js,內(nèi)容可參考文章配置:zhuanlan.zhihu.com/p/84329603 看第五點(diǎn)

          • Commit message 格式說明

          <type>: <subject>

          • type 值枚舉如下:

            • feat: 添加新特性
            • fix: 修復(fù) bug
            • docs: 僅僅修改了文檔
            • style: 僅僅修改了空格、格式縮進(jìn)、都好等等,不改變代碼邏輯
            • refactor: 代碼重構(gòu),沒有加新功能或者修復(fù) bug
            • perf: 增加代碼進(jìn)行性能測(cè)試
            • test: 增加測(cè)試用例
            • chore: 改變構(gòu)建流程、或者增加依賴庫、工具等
            • revert: 當(dāng)前 commit 用于撤銷以前的 commit
          • subject 是 commit 目的的簡短描述,不超過 50 個(gè)字符,且結(jié)尾不加句號(hào)(.)

          • package.json 新加入如下配置:

          {
              ...,
              "lint-staged": {
                  "src/**/*.{jsx,txs,ts,js,json,css,md}": [
                      "eslint --quiet"
                  ]
              },
          }
          • 可執(zhí)行 npx eslint [filePath] \--fix 進(jìn)行格式修復(fù),無法修復(fù)的需手動(dòng)解決

          三、項(xiàng)目配置一(功能配置)

          1. 安裝項(xiàng)目常用依賴庫

          $ yarn add antd axios dayjs qs -S # UI 庫 及工具庫

          $ yarn add react-router-dom redux react-redux redux-logger redux-thunk -S # 路由及狀態(tài)管理

          1. webpack 配置拓展很有必要
          • 根目錄新建 config-overrides.js,詳細(xì)使用可訪問:簡書:React 之 config-overrides文件配置
          • 安裝
          • $ yarn add react-app-rewired customize-cra -D

          • 修改 package.json 中啟動(dòng)項(xiàng)
          // package.json
          "scripts": {
              "start""react-app-rewired start",
              "build""react-app-rewired build",
          }
          • 使用
          // config-overrides.js
          const {
              override, // 主函數(shù)
              fixBabelImports, // 配置按需加載
              addWebpackExternals, // 不做打包處理配置
              addWebpackAlias, // 配置別名
              addLessLoader // lessLoader 配置,可更改主題色等
          } = require('customize-cra')

          module.exports = override(/* ... */, config => config)
          1. 配置按需加載
          // config-overrides.js
          ...
          module.exports = override(
              fixBabelImports('import', {
                  libraryName'antd',
                  libraryDirectory'es'// library 目錄
                  styletrue// 自動(dòng)打包相關(guān)的樣式
              }),
          )
          1. 更改主題色
          // config-overrides.js
          ...
          module.exports = override(
              addLessLoader({
                  lessOptions: {
                      javascriptEnabledtrue,
                      modifyVars: {
                          '@primary-color''#1890ff',
                      },
                  }
              }),
          )
          1. 別名配置(typescript 項(xiàng)目這里有坑)
          // config-overrides.js
          const path = require('path')
          ...
          module.exports = override(
              addWebpackAlias({
                  '@': path.resolve(__dirname, 'src'),
              }),
          )
          1. 去除注釋、多進(jìn)程打包壓縮
          // config-overrides.js
          const UglifyJsPlugin = require('uglifyjs-webpack-plugin')
          const HardSourceWebpackPlugin = require('hard-source-webpack-plugin')
          ...
          module.exports = override(/* ... */, config => {
              config.plugins = [...config.plugins, {
                  new UglifyJsPlugin({
                      uglifyOptions: {
                          warningsfalse,
                          compress: {
                              drop_debuggertrue,
                              drop_consoletrue,
                          },
                      },
                  }),
                  new HardSourceWebpackPlugin()
              }]
              return config
          })
          1. 解決埋下的兩個(gè)坑
          • 修改打包出的文件夾名為 dist
          // 修改打包路徑除了output,這里也要修改
          const paths = require('react-scripts/config/paths')
          paths.appBuild = path.join(path.dirname(paths.appBuild), 'dist')

          module.exports = override(/* ... */, config => {
              config.output.path = path.resolve(__dirname, 'dist')

              return config
          })
          • 解決 typescript 別名配置
            • 查閱相關(guān)資料,需要在 tsconfig.json 中添加一項(xiàng)配置
          {
              ...
              "extends""./paths.json"
          }
          • 新建文件 paths.json
          {
              "compilerOptions": {
                  "baseUrl""src",
                  "paths": {
                      "@/*": ["*"]
                  }
              }
          }
          1. 配置裝飾器寫法
          {
              "compilerOptions": {
                  "experimentalDecorators"true,
                  ...
              }
          }
          1. 配置開發(fā)代理
          • 在 src 目錄新建 setupProxy.js
          // src/setupProxy.js
          const proxy = require('http-proxy-middleware').createProxyMiddleware

          module.exports = function(app{
              // app 為 Express 實(shí)例,此處可以寫 Mock 數(shù)據(jù)
              app.use(
                  proxy('/api',
                  {
                      "target""https://qpj-test.fapiaoer.cn",
                      "changeOrigin"true,
                      "secure"false,
                      // "pathRewrite": {
                      //   "^/api": ""
                      // }
                  })
              )
          }
          1. 加入 polyfill 和 antd 組件國際化處理
          // src/index.tsx
          import React from 'react'
          import ReactDOM from 'react-dom'
          // 注入 store
          import { Provider } from 'react-redux'
          import store from '@/store/store'

          import { ConfigProvider, Empty } from 'antd'
          import App from './App'
          import zhCN from 'antd/es/locale/zh_CN'
          import 'moment/locale/zh-cn'

          // polyfill
          import 'core-js/stable'
          import 'regenerator-runtime/runtime'

          ReactDOM.render(
              <Provider store={store}>
                  <ConfigProvider locale={zhCN} renderEmpty={Empty}>
                  <App />
                  </ConfigProvider>
              </
          Provider>,
              document.getElementById('root')
          )
          1. CSS Modules

          create-react-app 自帶支持以 xxx.module.(c|le|sa)ss 的樣式表文件,使用上 typescript 項(xiàng)目中要注意:

          const styles = require('./index.module.less')

          retrun (
              <div className={`${styles.container}`}>
                  <Table
                      columns={columns}
                      className={`${styles['border-setting']}`}
                      dataSource={props.store.check.items}
                      rowKey={record => record.id}
                      pagination={false}
                  />
                  <div className="type-check-box"></div>
              </
          div>
          )
          // index.module.less
          .container {
          padding: 24px;
          background-color: #fff;
          height: 100%;
          overflow: auto;
          .border-setting {
          tr {
          td:nth-child(3) {
          border-left: 1px solid #F0F0F0;
          border-right: 1px solid #F0F0F0;
          }
          }
          td {
          text-align: left !important;
          }
          }
          :global { // 這個(gè)標(biāo)識(shí)之后,其子代元素可以不需要使用 `styles['type-check-box']` 的方式,直接寫 `className`
          .type-check-box {
          .ant-checkbox-wrapper + .ant-checkbox-wrapper{
          margin-left: 0;
          }
          }
          }
          }
          1. 【新】配置 React jsx 指令式屬性 r-ifr-forr-modelr-show,提升開發(fā)效率:
          • 安裝依賴

          $ yarn add babel-react-rif babel-react-rfor babel-react-rmodel babel-react-rshow -D

          • 配置 .babelrc :
          // .babelrc
          {
              ...,
              "plugins": [
                  "babel-react-rif",
                  "babel-react-rfor",
                  "babel-react-rmodel",
                  "babel-react-rshow",
              ]
          }
          • 使用示例:
          • r-if
          <div>
            <h1 r-if={height < 170}>good</h1>
            <h1 r-else-if={height > 180}>best</h1>
            <h1 r-else>other</h1>
          </div>
          • r-for
          {/* eslint-disable-next-line no-undef */}
          <div r-for={(item, index) in [1234]} key={index}>
            內(nèi)容 {item + '-' + index}
          </div>
          • r-model
          <input onChange={this.callback} type="text" r-model={inputVale} />
          • r-show
          <div r-show={true}>內(nèi)容</div> # 注意:這是 `r-if` 的效果,不會(huì)渲染節(jié)點(diǎn)

          四、項(xiàng)目配置二(優(yōu)化配置)

          1. 實(shí)現(xiàn)組件懶加載 react-loadable
          import Loadable from 'react-loadable'

          const Loading = (props: any) => {
              if (props.error) {
                  console.error(props.error)
                  return <div>Error! <Button type="link" onClick={props.retry}>Retry</Button></div>
              } else if (props.timedOut) {
                  return <div>Timeout! <Button onClick={props.retry}>Retry</Button></div>
              } else if (props.pastDelay) {
                  return <div>Loading...</div>
              } else {
                  return null
              }
          }

          const loadable = (path: any) => {
              return Loadable({
                  loader: () => import(`@/
          pages${path}`),
                  loading: Loading,
                  delay: 200,
                  timeout: 10000,
              })
          }

          const Home = loadable('/homePage/Home')

          1. 處理 axios 攔截響應(yīng)
          const service = axios.create({
              baseURL: '/',
              timeout: 15000,
          })

          service.interceptors.request.use(function (config{
              return config
          })

          service.interceptors.response.use(function (config{
              return config
          })
          1. 處理 React router 的嵌套配置

          我們知道 React 中不支持類似 Vue Router 路由配置方式,React 中一切皆組件,路由也是組件,需要用到路由要臨時(shí)加上路由組件,寫起來就很繁瑣,但我們可以自己實(shí)現(xiàn)路由配置表方式。

          // router/router.config.ts
          const routes = [
              {
                  path: '/home',
                  component: loadable('components/Index'),
                  exact: true,
              },
              {
                  path: '/new',
                  component: loadable('components/New'),
                  redirect: '/new/list',
                  // exact: true,
                  routes: [
                      {
                          path: '/new/list',
                          component: loadable('components/NewList'),
                          exact: true,
                      },
                      {
                          path: '/new/content',
                          component: loadable('components/NewContent'),
                          exact: true,
                      },
                  ],
              },
          ]

          export default routes
          // router/router.ts
          import React from 'react'
          import { Switch, BrowserRouter as Router, Route } from 'react-router-dom'
          import routes from './index'

          function mapRoutes(routes: any[], store: object): any {
              return routes.map((item: any, index: number) => {
                  return (
                      <Route exact={item.exact || false} path={item.path} key={index} render={props => {
                          const NewComp = item.component
                          Object.assign(props, {
                              redirect: item.redirect || null,
                              permission: item.permission || [],
                              ...store
                          })
                          if (item.routes) {
                              return <NewComp {...props}>{ mapRoutes(item.routes, store) }</NewComp>
                          } else {
                              return <NewComp {...props} /
          >
                          }
                      }} />
                  )
              })
          }

          const Routes = (props: any) => {
              return (
                  <Router>
                      <Switch>
                          { mapRoutes(routes, props.store) }
                          <Route component={() => (<div>404 Page not Found!</div>)} />
                      </Switch>
                  </
          Router>
              )
          }

          export default Routes

          子路由承載頁面需要加上如下代碼:

          import { Redirect, Route, Switch } from 'react-router-dom'

          <Switch>
              {props.children}
              <Route component={() => (<div>404 Page not Found!</div>)} />
              {props.redirect && <Redirect to={props.redirect} />}
          </Switch>

          1. 處理 React store
          // store/store.ts
          import { createStore, applyMiddleware } from 'redux'
          import thunk from 'redux-thunk'
          import logger from 'redux-logger'
          import reducers from './reducer'

          const store = process.env.NODE_ENV === 'development'
              ? createStore(reducers, applyMiddleware(thunk, logger))
              : createStore(reducers, applyMiddleware(thunk))

          export default store

          為了方便使用,避免每個(gè)組件都需要 connect ,這邊實(shí)現(xiàn)了 redux store 的全局注入,但是如果項(xiàng)目龐大的話就會(huì)損耗些性能。

          // App.tsx
          import { dispatchActions } from '@/store/reducer'

          export default connect((state: any) => ({ store: state }), dispatchActions)(App)

          五、總結(jié)

          自此項(xiàng)目搭建就全部完成了,剩下的就是業(yè)務(wù)代碼了。相信你可以得到如下收獲:
          ① 項(xiàng)目構(gòu)建在宏觀上有個(gè)極大的能力提升;
          ② 項(xiàng)目整體功能了解清晰;
          ③ 排查問題不慌亂;
          ④ 封裝能力有加強(qiáng);
          ⑤ 業(yè)務(wù)功能很清楚。

          六、題外話

          基于 create-react-app 創(chuàng)建的 React 項(xiàng)目,本人實(shí)現(xiàn)了一個(gè)腳手架,以上配置默認(rèn)已經(jīng)全部加入實(shí)現(xiàn),歡迎 Github 試用并 star 。鏈接:https://github.com/zhuyuanmin/zym-cli


          關(guān)于本文

          作者:前端小猿_zym

          https://juejin.cn/post/6953807616082460702



          The End

          歡迎自薦投稿到《前端技術(shù)江湖》,如果你覺得這篇內(nèi)容對(duì)你挺有啟發(fā),記得點(diǎn)個(gè) 「在看」


          點(diǎn)個(gè)『在看』支持下 

          瀏覽 93
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          評(píng)論
          圖片
          表情
          推薦
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  中文一级片 | 婷婷丁香在线 | 日韩无码高清免费 | 成人免费视频在线观看 | www.黄色毛片 |