1. 程式人生 > >記使用react全家桶過程中遇到的坑

記使用react全家桶過程中遇到的坑

前言:最近在做一個新專案,老師讓我們用一下比較先進的前端技術,所以就選擇了React 從此走上了一條踩坑的道路

搭建環境

使用Webpack 加node

什麼是Webpack

WebPack可以看做是模組打包機:它做的事情是,分析你的專案結構,找到JavaScript模組以及其它的一些瀏覽器不能直接執行的拓展語言(Scss,TypeScript等),並將其轉換和打包為合適的格式供瀏覽器使用。

WebPack和Grunt以及Gulp相比有什麼特性

其實Webpack和另外兩個並沒有太多的可比性,Gulp/Grunt是一種能夠優化前端的開發流程的工具,而WebPack是一種模組化的解決方案,不過Webpack的優點使得Webpack在很多場景下可以替代Gulp/Grunt類的工具。

Grunt和Gulp的工作方式是:在一個配置檔案中,指明對某些檔案進行類似編譯,組合,壓縮等任務的具體步驟,工具之後可以自動替你完成這些任務。

Grunt和Gulp的工作方式是:在一個配置檔案中,指明對某些檔案進行類似編譯,組合,壓縮等任務的具體步驟,工具之後可以自動替你完成這些任務。
這裡寫圖片描述
Webpack的工作方式是:把你的專案當做一個整體,通過一個給定的主檔案(如:index.js),Webpack將從這個檔案開始找到你的專案的所有依賴檔案,使用loaders處理它們,最後打包為一個(或多個)瀏覽器可識別的JavaScript檔案。
這裡寫圖片描述

開始使用webpack

npm install
-g webpack #-g全域性安裝 便於在命令列使用該命令 npm install --save -dev #在你建立的目錄下使用該命令

PS:如果國內npm 下載慢的話 可以用淘寶的cnpm 具體方法可以網上看教程

npm init   #建立一個package.json檔案

這是一個標準的npm說明檔案,裡面蘊含了豐富的資訊,包括當前專案的依賴模組,自定義的指令碼任務等等。

檔案目錄結構

大致的檔案目錄結構(名字可以隨意取,功能分開就好)
/app是程式的入口所有的邏輯程式碼 全部放這裡面
/doc 是一些md檔案 自己寫的說明可以放這裡面
/mock 服務端檔案 本例用於互動測試
/node_modules 下載的依賴包

使用webpack構建本地伺服器

npm install --save-dev webpack-dev-serve

devserver的配置選項
contentBase 預設webpack-dev-server會為根資料夾提供本地伺服器,如果想為另外一個目錄下的檔案提供本地伺服器,應該在這裡設定其所在目錄(本例設定到“public”目錄)

port 設定預設監聽埠,如果省略,預設為”8080“
inline 設定為true,當原始檔改變時會自動重新整理頁面
historyApiFallback 在開發單頁應用時非常有用,它依賴於HTML5 history API,如果設定為true,所有的跳轉將指向index.html

webpack.config.js
webpack重要的配置檔案

module.exports = {
  devtool: 'eval-source-map',

  entry:  __dirname + "/app/main.js",
  output: {
    path: __dirname + "/public",
    filename: "bundle.js"
  },

  devServer: {
    contentBase: "./public",//本地伺服器所載入的頁面所在的目錄
    historyApiFallback: true,//不跳轉
    inline: true//實時重新整理
  } 
}
"scripts": {
    "test": "echo \"Error: no test specified\" && exit 1",
    "start": "webpack",
    "server": "webpack-dev-server --open"
  },

在終端中輸入npm run server即可在本地的8080埠檢視結果

Loaders是webpack提供的最激動人心的功能之一了。通過使用不同的loader,webpack有能力呼叫外部的指令碼或工具,實現對不同格式的檔案的處理,比如說分析轉換scss為css,或者把下一代的JS檔案(ES6,ES7)轉換為現代瀏覽器相容的JS檔案,對React的開發而言,合適的Loaders可以把React的中用到的JSX檔案轉換為JS檔案。

Loaders需要單獨安裝並且需要在webpack.config.js中的modules關鍵字下進行配置,Loaders的配置包括以下幾方面:

  • test:一個用以匹配loaders所處理檔案的拓展名的正則表示式(必須)
  • loader:loader的名稱(必須)
  • include/exclude:手動新增必須處理的檔案(資料夾)或遮蔽不需要處理的檔案(資料夾)(可選);
  • query:為loaders提供額外的設定選項(可選)

Babel

Babel其實是一個編譯JavaScript的平臺,它的強大之處表現在可以通過編譯幫你達到以下目的:

  • 使用下一代的JavaScript程式碼(ES6,ES7…),即使這些標準目前並未被當前的瀏覽器完全的支援;
  • 使用基於JavaScript進行了拓展的語言,比如React的JSX;
#安裝Babel全家桶
npm install --save-dev babel-core babel-loader babel-preset-es2015 babel-preset-react

在webpack中配置Babel的方法如下:

module.exports = {
    entry: __dirname + "/app/main.js",//已多次提及的唯一入口檔案
    output: {
        path: __dirname + "/public",//打包後的檔案存放的地方
        filename: "bundle.js"//打包後輸出檔案的檔名
    },
    devtool: 'eval-source-map',
    devServer: {
        contentBase: "./public",//本地伺服器所載入的頁面所在的目錄
        historyApiFallback: true,//不跳轉
        inline: true//實時重新整理
    },
    module: {
        rules: [
            {
                test: /(\.jsx|\.js)$/,
                use: {
                    loader: "babel-loader",
                    options: {
                        presets: [
                            "es2015", "react"
                        ]
                    }
                },
                exclude: /node_modules/
            }
        ]
    }
};

CSS loader

webpack提供兩個工具處理樣式表,css-loader 和
style-loader,二者處理的任務不同,css-loader使你能夠使用類似@import 和 url(…)的方法實現
require()的功能,style-loader將所有的計算後的樣式加入頁面中,二者組合在一起使你能夠把樣式表嵌入webpack打包後的JS檔案中。

#安裝css的loader 
npm install --save-dev style-loader css-loader
module.exports = {

   ...
    module: {
        rules: [
            {
                test: /(\.jsx|\.js)$/,
                use: {
                    loader: "babel-loader"
                },
                exclude: /node_modules/
            },
            {
                test: /\.css$/,
                use: [
                    {
                        loader: "style-loader"
                    }, {
                        loader: "css-loader"
                    }
                ]
            }
        ]
    }
};

下面就是我遇到的第一個坑

Webpack 2.1之前 外掛的配置是這樣的
這裡寫圖片描述
eslint 和 postcss 是放在plugins外面的
PS:loader後面跟的具體的loader 必須寫完整 不能只寫babel 或者style 必須寫成 babel-loader和style-loader
然後就會這樣:

For loader options: webpack 2 no longer allows custom properties in configuration.
     Loaders should be updated to allow passing options via loader options in module.rules.
     Until loaders are updated one can use the LoaderOptionsPlugin to pass these options to the loader:
     plugins: [
       new webpack.LoaderOptionsPlugin({
         // test: /\.xxx$/, // may apply this only for some modules
         options: {
           postcss: ...
         }
       })
     ]

也就是說,Webpack 2.1.0-beta23之後的版本,不能直接包含自定義配置項

你得這樣:

 plugins: [
        new webpack.LoaderOptionsPlugin({
            options: {
                postcss: [
                    require('autoprefixer')//呼叫autoprefixer外掛
                ]               
            }
        })      
    ]

當時真的原地爆炸

接下來進入用React仿大眾點評的專案

用React + redux3.6 + react-router 4.0 +fetch2.0

這裡寫圖片描述

專案架構

入口檔案是index.jsx

首先寫的是主頁面也就是Home
首先做好路由

然後就遇到了第二個坑 踩了很久的坑

router 2.x的時候
路由是可以巢狀的
這裡寫圖片描述
像這樣

而router4.x的時候
他是這樣的
這裡寫圖片描述

有幾個大的不同

  1. Route不支援巢狀必須寫在同級 意思就是如果Home層中還有路由跳轉你不能寫在App層中Home Route的下面而是得寫在Home檔案中巢狀
  2. 取消了Router的寫法 改成HashRouter 和BrowserRouter
  3. switch 只渲染出第一個與當前訪問地址匹配的 Route 或 Redirect。另外,Switch對於轉場動畫也非常適用,因為被渲染的路由和前一個被渲染的路由處於同一個節點位置!
  4. 從react-router-dom中引用

在這裡栽了很久 因為是初次接觸React 網上的教程大多數是2.x的 所以 你懂得

然後就是理解使用Redux

  • 使用者的使用方式複雜
  • 不同身份的使用者有不同的使用方式(比如普通使用者和管理員)
  • 多個使用者之間可以協作
  • 與伺服器大量互動,或者使用了WebSocket
  • View要從多個來源獲取資料

當遇到以上場景時需要使用Redux

Redux有幾個主要的API

store
Store 就是儲存資料的地方,你可以把它看成一個容器。整個應用只能有一個 Store。
Redux 提供createStore這個函式,用來生成 Store。

import { createStore } from 'redux';
const store = createStore(reducer);

state
Store物件包含所有資料。如果想得到某個時點的資料,就要對 Store 生成快照。這種時點的資料集合,就叫做 State。
當前時刻的 State,可以通過store.getState()拿到。
State是整個應用的資料,本質上是一個普通物件。
State決定了整個應用的元件如何渲染,渲染的結果是什麼。可以說,State是應用的靈魂,元件是應用的肉體。

Action
State 的變化,會導致 View 的變化。但是,使用者接觸不到 State,只能接觸到 View。所以,State 的變化必須是 View 導致的。Action 就是 View 發出的通知,表示 State 應該要發生變化了。
Action 是一個物件。其中的type屬性是必須的,表示 Action 的名稱。其他屬性可以自由設定,社群有一個規範可以參考。

Action Creator
View 要傳送多少種訊息,就會有多少種 Action。如果都手寫,會很麻煩。可以定義一個函式來生成 Action,這個函式就叫 Action Creator。

const ADD_TODO = '新增 TODO';

function addTodo(text) {
  return {
    type: ADD_TODO,
    text
  }
}

const action = addTodo('Learn Redux');

dispatch
state無法直接接觸到action 需要通過dispatch來觸發 告訴state進行怎麼樣的改變

import { createStore } from 'redux';
const store = createStore(fn);

store.dispatch({
  type: 'ADD_TODO',
  payload: 'Learn Redux'
});

Reducer
Store 收到 Action 以後,必須給出一個新的 State,這樣 View 才會發生變化。這種 State 的計算過程就叫做 Reducer。
Reducer 是一個函式,它接受 Action 和當前 State 作為引數,返回一個新的 State。

當Reduer數量過於龐大的時候 寫在一個reducer中會很臃腫 所以就需要拆分成多個reducer
而Redux同樣提供了combineReducers函式 可以將多個reducer 合併成一個返回給state

import { combineReducers } from 'redux';

const chatReducer = combineReducers({
  chatLog,
  statusMessage,
  userName
})

export default todoApp;

藉助了阮一峰的部落格進行梳理 寫的很清楚
梳理一下Redux的工作流程

使用者只能看見View層 對View層進行操作 然後View層會發出一個action

store通過dispatch來觸發相應的reducer

reducer接受兩個引數 之前的state 也就是之前的狀態 然後進行對應的操作 返回一個新的state 也就是一個新的狀態

PS:state改動時會讓整個元件進行重新渲染 以達到更改”狀態”的目的,將新的狀態顯示給使用者看 而我們改變的state可能只是部分元件的state進行改變 而不是整個專案 如果整個專案重新渲染的話 時間的開銷會很大 使用者體驗可能也不會好

可以利用一個外掛來優化這個問題
react-addons-pure-render-mixin

如果你的React元件的渲染函式是一個純函式也就是說對於相同的值返回一樣的結果同時不影響元素局,在某些場景下,你可以利用這個外掛來極大地提升效能。

在底層,該外掛實現了shouldComponentUpdate,在這裡面,它比較當前的props、state和接下來的props、state,當兩者相等的時候返回false,不進行更新。

這裡寫圖片描述

constructor是一個構造器就是在這個元件被渲染的時候進行的操作

這樣就可以實現 哪裡更改了更新哪裡 而不是全域性重新渲染 節省了時間的開銷

暫時先到這裡 初學React 可能會有很多錯誤 歡迎指出
踩坑之路還在繼續