[譯】Redux入門教程(二)

夏天來嘍發表於2019-05-13

原Redux教程目錄:

[譯】Redux入門教程(二)
上一篇文章更新了目錄的1-10, 這篇教程更新11-17,看不懂的小夥伴一定要先去看第一篇

甩上一篇教程地址 【譯】Redux入門教程(一)

本次更新目錄一覽

重構reducer

在繼續學習之前,來看看Redux的主要概念:

  • Redux的store是大腦:它掌管Redux所有部分
  • 應用的狀態通過store存在一個不可變的物件中
  • 一旦store收到了一個action, 它會觸發一個reducer函式
  • 這個reducer返回下一個state

Redux的reducer是由什麼組成的?

一個reducer是一個Javascript函式,它接收兩個引數: state和action, 一個reducer函式可以使用一個switch語句(但是我更傾向於使用if語句)來處理每個ation型別

reducer通過action的type來計算下一個state, 而且,當沒有匹配到action的type的時候,它至少應該返回初始的state

當action的type匹配到一個有效的語句的時候,reducer就會計算下一個state並且返回一個新的物件

在之前的章節中,我們建立了一個reducer,它除了返回初始的state什麼也沒做

開啟src/js/reducers/index.js,並且更新如下:

// src/js/reducers/index.js
import { ADD_ARTICLE } from "../constants/action-types";
const initialState = {
  articles: []
};
function rootReducer(state = initialState, action) {
  if (action.type === ADD_ARTICLE) {
    state.articles.push(action.payload);
  }
  return state;
}
export default rootReducer;
複製程式碼

你發現了什麼?

儘管上面是有效的程式碼,但是他違背了Redux的原則:不變性(immutability)

陣列的push方法不是一個純函式:它改變了原陣列,還有更嚴重的問題! 你記得Redux的第三個原則嗎?state是不可變的並且任何時候都不能改變, 在我們的reducer裡面我們改變了初始的物件!

我們需要修復。首先我們返回一個新的state,使用Object.assign得到的一個新的JavaScript物件, 通過這種方法,我們保證了原始的state的不變性, 接下來我們使用原生陣列的conat方法代替push方法來保證原始陣列不變,最終改造如下:

import { ADD_ARTICLE } from "../constants/action-types";
const initialState = {
  articles: []
};
function rootReducer(state = initialState, action) {
  if (action.type === ADD_ARTICLE) {
    return Object.assign({}, state, {
      articles: state.articles.concat(action.payload)
    });
  }
  return state;
}
export default rootReducer;
複製程式碼

上面的例子保證了state是沒有被修改的

初始的articels沒有改變

初始的state物件也沒有改變, 返回的state是初始state的一個拷貝

在Redux中避免變化有兩個要點:

  • 對陣列使用concat, slice和...展開運算
  • 對物件使用Objecy.assign和...展開運算

Redux技巧: 隨著你的app越來越大,reducer也會越來越大,你可以將一個大的reducer函式分割成多個函式,並且使用combineReducers將他們結合起來

接下來,我們將會在控制檯玩Redux, 跟緊我的腳步!

Redux store的方法

我怕保證,下面的內容很快就可以學會

我想要你通過瀏覽器的控制檯快速理解Redux是怎麼工作的

Redux本身是一個小型庫(2kKB), Redux的store暴露了簡單的API來管理state, store最重要的方法是:

  • getState 獲取應用的當前狀態
  • dispatch 派發一個action
  • subscribe 監聽state的變化

我們將會在瀏覽器的控制檯學習這些方法

為次,我們必須將我們之前建立的store和action暴露成全域性變數

建立一個src/js/index.js檔案,更新如下:

import store from "../js/store/index";
import { addArticle } from "../js/actions/index";
window.store = store;
window.addArticle = addArticle;
複製程式碼

現在開啟src/index.js, 用下面的內容代替原先的內容:

import index from "./js/index"
複製程式碼

現在啟動專案:

npm run start
複製程式碼

前往瀏覽器,開啟http://localhost:8080/,並且按下F12開啟控制檯

因為我們已經將store暴露為全域性變數了,我們可以獲取到它的方法,試試吧!

開始獲取當前的state:

store.getState()
複製程式碼

輸出:

{articles: Array(0)}
複製程式碼

articles是空陣列, 事實上,我們還沒更新初始state

subscribe方法接受一個回撥函式,當action被派發的時候這個回撥函式會被觸發,派發一個action就是通知store我們想要改變state

用下面的程式碼來註冊回撥函式:

store.subscribe(() => console.log('Look ma, Redux!!'))
複製程式碼

在Redux中要改變state,我們需要派發action, 派發action需要呼叫dispath方法

我們有一個action: addArticle-往state裡面新增一項

用下面的程式碼派發action

store.dispatch(addArticle({ title: 'React Redux Tutorial for Beginners', id: 1 }) )
複製程式碼

當你執行上面的程式碼後,你就會看到

Look ma, Redux!!
複製程式碼

為了驗證state真的發生了變化,再次執行store.getState() 輸出{articles: Array(1)}

這就是Redux最簡單的形式了

難嗎?

一步步來探索這三個Redux方法,在控制檯試驗它們

  • getState 獲取應用的當前狀態
  • dispatch 派發一個action
  • subscribe 監聽state的變化

這就是開始學習Redux你需要知道的所有東西

一旦你覺得有信心繼續學習下面的內容了,我們將開始連線React和Redux!

連線React和Redux

學完Redux,我就意識到它並不複雜,我知道通過getState獲取當前的state, 我知道怎麼用dispatch派發一個action, 怎麼用subscribe監聽state的改變

然後我還是不知道怎樣將React和Redux結合在一起

我問我自己:我應該在一個React的元件中呼叫getState嗎?我怎麼在一個React元件中派發一個action?等等這些問題都困擾著我

Redux本身是一個獨立的框架,你可以在普通的Javascript,或者框架如Angular,React中使用它,有種東西可以結合Redux和你最喜歡的框架。

對於React來說就是react-redux

繼續學習之前,先安裝react-redux npm i react-redux --save-dev

為了演示React和Redux是怎麼協同工作的,我們來建一個超級簡單的應用,這個應用由下面的元件組成:

  • 一個App元件
  • 一個展示所有文章的列表元件
  • 一個新增文章的表單元件

react-redux是什麼

react-redux是一個將Redux繫結到React的輕巧庫

接下來你將要認識一個很重要的方法connect

react-redux的connect到底做了什麼?豪不驚訝,他將React的元件和Redux的store聯絡了起來

使用connect, 你可以傳遞兩個或者三個引數,具體取決於用途,下面是需要知道的基本的東西

  • mapStateToProps函式
  • mapDispatchToProps函式

react-redux中mapStateToProps是幹什麼用的?mapStateToProps 的作用正如它的名字:它連線Redux state的一部分到React元件的props,這樣,一個連線的React元件就可獲取到它所需要的那部分store的資料

mapDispatchToProps又是幹什麼用的呢?它做了和mapStateToProps 相似的事,但是是針對actions的, ,這樣,一個連線的React元件就可以派發actions了

現在一切是不是都清楚了?如果不清楚,停下來,重新讀這份教程,我知道,有很多東西要學,要花很多時間,即使你不能馬上學會Redux也不要焦慮, 一切遲早都會清晰明瞭。

從下面開始,我們要做點東西了!

App component 和Redux store

我們已經知道,mapStateToProps連線Redux state的一部分到React元件的props中,你可能想知道,它可以連線整個Redux和React嗎?是的,它不能 要想將Redux和React連線起來,我們需要使用Provider

Provider是一個來自react-redux的高階元件

用外行人的話來說,Provider包裹你的React應用, 並且讓它可以感知到整個Redux的store

為什麼要這樣?我們知道Redux的store管理一切,React一定要可以和store溝通才可以獲取state和派發actions

開啟src/js/index.js, 清除整個檔案內容,替換成下面的內容(如果你是通react-create-app來搭建環境的話,你需要修改的是src/index.js這個檔案)

import React from "react";
import { render } from "react-dom";
import { Provider } from "react-redux";
import store from "./store/index";
import App from "./components/App.jsx";
// if you're in create-react-app import the files as:
// import store from "./js/store/index";
// import App from "./js/components/App.jsx";
render(
  <Provider store={store}>
    <App />
  </Provider>,
  // The target element might be either root or app,
  // depending on your development environment
  // document.getElementById("app")
  document.getElementById("root")
);
複製程式碼

看到了嗎?Provider 包裹了整個React應用,而且它獲取store作為prop傳遞

現在我們建立一個App元件,因為我們馬上就需要它了,這個元件沒什麼特別:引入 List元件並且渲染自己

建立一個放元件的目錄:

mkdir -p src/js/components
複製程式碼

在這目錄裡面增加一個App.jsx檔案:

// src/js/components/App.jsx
import React from "react";
import List from "./List";
const App = () => (
  <div className="row mt-5">
    <div className="col-md-4 offset-md-1">
    <h2>Articles</h2>
      <List />
    </div>
  </div>
);
export default App;
複製程式碼

儲存檔案,接下來建立List元件

List component 和 Redux state

目前為止,我們做的都沒什麼特別的

但是我們的新元件List將會和Redux的store互動

簡要回顧:連線React元件和Redux的關鍵是connect

connect接收至少一個引數

因為我們想要List元件獲取文章列表,也就是要連線state.articles到這個元件中,怎麼做呢?用mapStateToProps

在 src/js/components建立一個List.jsx檔案,內容如下

import React from "react";
import { connect } from "react-redux";
const mapStateToProps = state => {
  return { articles: state.articles };
};
const ConnectedList = ({ articles }) => (
  <ul className="list-group list-group-flush">
    {articles.map(el => (
      <li className="list-group-item" key={el.id}>
        {el.title}
      </li>
    ))}
  </ul>
);
const List = connect(mapStateToProps)(ConnectedList);
export default List;
複製程式碼

List元件接收articels作為prop, articles是Redux的state的articels的一個拷貝, 它來自於reducer

const initialState = {
  articles: []
};
function rootReducer(state = initialState, action) {
  if (action.type === ADD_ARTICLE) {
    return Object.assign({}, state, {
      articles: state.articles.concat(action.payload)
    });
  }
  return state;
}
複製程式碼

時刻牢記:redux的state來自於reducers, 現在需要利用JSX的prop來生成articles列表

{articles.map(el => (
  <li className="list-group-item" key={el.id}>
    {el.title}
  </li>
))}****
複製程式碼

React技巧:養成用PropTypes 驗證props的習慣,或者使用TypeScript會更好

最後,元件將List匯出, List是無狀態元件ConnectedList和Redux store結合的結果

是否依然困惑?我也是,理解connect如何運作需要一些時間,不要怕, 通往Redux的學習之路充滿了'啊?-啊!'的時刻

我建議你休息一下,再探索研究connect和mapStateToProps

一旦你對這些都胸有成竹了,你可以繼續進行下面的學習了!

Form component 和 Redux actions

我們即將建立的Form元件比List元件複雜一點,它是一個表單,可以新增新條目到應用中

除此之外,它是一個有狀態的元件

一個有狀態的元件是一個有自己的state的React元件

我們現在在談論用Redux管理狀態,你為什麼要給Form元件自身狀態呢?

即使使用Redux,我們也可以有有狀態的元件存在

並不是每個應用的狀態都應該放在Redux中

在這個例子中,我不想要其他的元件知道這個Form元件本自身的state

form元件包含一些通過提交操作跟新本地狀態的邏輯

它接收一個Redux的action,這樣,它可以通過派發addArticle這個action更新全域性的state

在src/js/components新增Form.jsx , 內容如下

// src/js/components/Form.jsx
import React, { Component } from "react";
import { connect } from "react-redux";
import uuidv1 from "uuid";
import { addArticle } from "../actions/index";
function mapDispatchToProps(dispatch) {
  return {
    addArticle: article => dispatch(addArticle(article))
  };
}
class ConnectedForm extends Component {
  constructor() {
    super();
    this.state = {
      title: ""
    };
    this.handleChange = this.handleChange.bind(this);
    this.handleSubmit = this.handleSubmit.bind(this);
  }
  handleChange(event) {
    this.setState({ [event.target.id]: event.target.value });
  }
  handleSubmit(event) {
    event.preventDefault();
    const { title } = this.state;
    const id = uuidv1();
    this.props.addArticle({ title, id });
    this.setState({ title: "" });
  }
  render() {
    const { title } = this.state;
    return (
      <form onSubmit={this.handleSubmit}>
        <div className="form-group">
          <label htmlFor="title">Title</label>
          <input
            type="text"
            className="form-control"
            id="title"
            value={title}
            onChange={this.handleChange}
          />
        </div>
        <button type="submit" className="btn btn-success btn-lg">
          SAVE
        </button>
      </form>
    );
  }
}
const Form = connect(null, mapDispatchToProps)(ConnectedForm);
export default Form;
複製程式碼

除了mapDispatchToProps和connect,這個元件是標準的React元件

mapDispatchToProps連線Redux的actions到React元件的props,這樣,一個連線的元件就可以派發actions了

通過handleSubmit方法你可以明白action是如何被派發的

// ...
  handleSubmit(event) {
    event.preventDefault();
    const { title } = this.state;
    const id = uuidv1();
    this.props.addArticle({ title, id }); // Relevant Redux part!!
// ...
  }
// ...
複製程式碼

最後元件匯出為Form,它是ConnectedForm和Redux store結合後的結果

注意:當mapStateToProps 不存在的時候,connect的第一個引數必須是null,否則你會得到TypeError:dispatch 不是一個函式

我們的元件都設定好了!

更新App元件,引入Form 元件

import React from "react";
import List from "./List.jsx";
import Form from "./Form.jsx";
const App = () => (
  <div className="row mt-5">
    <div className="col-md-4 offset-md-1">
      <h2>Articles</h2>
      <List />
    </div>
    <div className="col-md-4 offset-md-1">
      <h2>Add a new article</h2>
      <Form />
    </div>
  </div>
);
export default App;
複製程式碼

安裝uuid

npm i uuid --save-dev
複製程式碼

執行應用

npm run start
複製程式碼

前往瀏覽器開啟http://localhost:8080,你會看到如下的頁面

[譯】Redux入門教程(二)
頁面沒有任何奇特的東西,但是它顯示了React和Redux正在工作!

左邊的List元件連線著Redux的store,當你新增一個條目的時候,它會重新渲染

[譯】Redux入門教程(二)
如果你在頁面上什麼都沒看到,確保你在 src/js/index.js中寫了document.getElementById(“app”) ****來匹配一個頁面中真實存在的元素

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="utf-8">
    <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0-beta.2/css/bootstrap.min.css" >
    <title>How to set up React, Webpack, and Babel</title>
</head>
<body>
    <div class="container">
        <div id="root">
        </div>
    </div>
</body>
</html>
複製程式碼

別忘記引入Bootstrap ,但是我們還沒做完,接下來,我們來看看Redux的中介軟體,堅持住!


本次更新完畢,有錯誤和翻譯不準確的地方,歡迎大家指出,一起學習進步!!!

近期文章:

相關文章