1. 程式人生 > >React-Redux 中文文件

React-Redux 中文文件

介紹

快速開始

React-ReduxRedux的官方React繫結庫。它能夠使你的React元件從Redux store中讀取資料,並且向store分發actions以更新資料

安裝

在你的React app中使用React-Redux:

npm install --save react-redux

或者

yarn add react-redux

Providerconnect

React-Redux 提供<Provider/>元件,能夠使你的整個app訪問到Redux store中的資料:

import React from "react";
import ReactDOM from "react-dom";

import { Provider } from "react-redux";
import store from "./store";

import App from "./App";

const rootElement = document.getElementById("root");
ReactDOM.render(
  <Provider store={store}>
    <App />
  </Provider>,
  rootElement
);

React-Redux提供一個connect方法能夠讓你把元件和store連線起來。

通常你可以以下面這種方式呼叫connect方法:

import { connect } from "react-redux";
import { increment, decrement, reset } from "./actionCreators";

// const Counter = ...

const mapStateToProps = (state /*, ownProps*/) => {
  return {
    counter: state.counter
  };
};

const mapDispatchToProps = { increment, decrement, reset };

export default connect(
  mapStateToProps,
  mapDispatchToProps
)(Counter);

基礎教程

為了進一步瞭解如何實際使用React-Redux,我們將一步一步建立一個todo listapp

一個Todo List例項

跳轉到:

React UI 元件

我們所用到的React UI元件如下:

  • TodoApp:我們應用的入口元件,它renderAddTodo,TodoListVisibilityFilters元件
  • AddTodo:允許使用者在點選Add Todo按鈕後,向todo list中加入一個新的待辦項:

    • 使用一個受控input監聽onChange事件以設定state
    • 當用戶單擊Add Todo按鈕後,該元件dispatch一個action,向store
      中新增一個新的待辦項。(這個action是我們由React-Redux提供的)
  • TodoList:渲染出待辦項列表的元件:

    • 當一個VisibilityFilter被選擇後,能夠渲染出所匹配的待辦項列表
  • Todo:僅負責渲染單個todo待辦項:

    • 渲染出待辦項的內容,通過橫貫線表示該項已被完成
    • 觸發onClick事件後,dispatch一個能切換完成狀態的action
  • VisibilityFilters:渲染一個filters集合:_all_,_complete_ 以及 _incomplete_。單擊每一項能夠篩選匹配的todos:

    • 從父元件接收一個activeFilter屬性以表示當前使用者選擇的過濾條件。選中的filter會顯示出下劃線。
    • 能夠dispatch名為setFilteraction以更新已選過濾條件
  • constants:儲存我們的app所有需要的常量資料
  • 最後,index將app渲染到DOM中

the Redux Store

  • Store:

    • todos:標準化的todos的reducer。包含了byIds的待辦項map物件結構,和一個包含了所有待辦項id的allIds陣列
    • visibilityFilters:簡單的字串all, completed, or incomplete.
  • Action Creators:

    • addTodo:建立增添待辦項的action。接收一個string變數content,返回ADD_TODO型別的action,以及一個payload物件(包含了自增的idcontent屬性)
    • toggleTodo:建立一個切換待辦項的action。只接收一個number型別的變數id,返回TOGGLE_TODO型別action以及僅含id屬性的payload物件。
    • setFilter:建立設定app當前過濾條件的action。接收一個string型別變數filter返回一個SET_FILTER型別action一集一個包含filter自身的payload物件。
  • Reducers

    • todos reducer:

      
      - 在接收到`ADD_TODO` action時,將`id`追加到`allIds`陣列,並且更新`byIds`
      - 在接收到`TOGGLE_TODO` action時,切換`completed`狀態
      
    • VisibilityFilters reducer:在接收到SET_FILTERaction 時負責更新VISIBILITY_FILTERS狀態
  • Action Types

    • 使用一個actionTypes.js檔案來儲存所有的action types常量,以便複用
  • Selectores

    • getTodoList:從todos store中返回allIds列表
    • getTodoById:通過id查詢store中的todo項
    • getTodos:有點複雜。它接收allIds陣列中的所有id,找到每一個對應的byIds中的todo,返回最終的todos陣列
    • getTodosByVisibilityFilter:根據篩選條件過濾todos

你可以檢視上面這些UI元件的和尚未連線的Redux Store原始碼

下面我們將展示如何使用React-Reduxstore連線到我們的app中

建立Store

首先我們需要讓store成為我們app中可訪問的物件。為此,我們將用React-Redux提供給我們的<Provider/>元件包裹我們的根元件

// index.js
import React from "react";
import ReactDOM from "react-dom";
import TodoApp from "./TodoApp";

import { Provider } from "react-redux";
import store from "./redux/store";

const rootElement = document.getElementById("root");
ReactDOM.render(
  <Provider store={store}>
    <TodoApp />
  </Provider>,
  rootElement
);

要注意到store是以一個prop<Provider/>傳遞到被包裹的<TodoApp/>中的

連線元件

React-Redux提供一個connect方法使你可以從Redux store中讀取資料(以及當store更新後,重新讀取資料)

connect方法接收兩個引數,都是可選引數:

  • mapStateToProps:每當store state發生變化時,就被呼叫。接收整個store state,並且返回一個該元件所需要的資料物件
  • mapDispatchToProps:這個引數可以是一個函式物件

    • 如果是一個函式,一旦該元件被建立,就會被呼叫。接收dispatch作為一個引數,並且返回一個能夠使用dispatch來分發actions的若干函式組成的物件
    • 如果是一個action creators構成的物件,每一個action creator將會轉化為一個prop function並會在呼叫時自動分發actions。注意: 我們建議使用這種形式。

通常,你可以這樣去connect

const mapStateToProps = (state, ownProps) => ({
  // ... 從state中處理的一些資料,以及可選的ownProps
});

const mapDispatchToProps = {
  // ... 通常是action creators構成的物件
};

// `connect`返回一個新的函式,可以接收一個待包裝的元件
const connectToStore = connect(
  mapStateToProps,
  mapDispatchToProps
);
// 上面的函式能夠返回一個已經包裝、連線過的元件
const ConnectedComponent = connectToStore(Component);

// 我們通常寫成一條語句如下:
connect(
  mapStateToProps,
  mapDispatchToProps
)(Component);

下面讓我們開始編寫<AddTodo/>。它要能夠觸發store的變化從而增加新的todos。因此,他要能夠向store dispatch actions。下面就是具體流程。

我們的addTodo action建立函式如下所示:

// redux/actions.js
import { ADD_TODO } from "./actionTypes";

let nextTodoId = 0;
export const addTodo = content => ({
  type: ADD_TODO,
  payload: {
    id: ++nextTodoId,
    content
  }
});

// ... other actions

把它傳遞到connect,我們的元件就能夠以一個prop接收到它。並且一旦我們呼叫,它就能夠自動的分發actions

// components/AddTodo.js

// ... other imports
import { connect } from "react-redux";
import { addTodo } from "../redux/actions";

class AddTodo extends React.Component {
  // ... component implementation
}

export default connect(
  null,
  { addTodo }
)(AddTodo);

注意到現在<AddTodo/>已經被一個父元件<Connect(AddTodo)/>所包裝。同時,<AddTodo/>現在擁有了一個propaddTodo action

我們也需要實現handleAddTodo方法以便分發addTodo action 並且重置input

// components/AddTodo.js

import React from "react";
import { connect } from "react-redux";
import { addTodo } from "../redux/actions";

class AddTodo extends React.Component {
  // ...

  handleAddTodo = () => {
    // 分發action以增加todo項
    this.props.addTodo(this.state.input);

    // 將state的input置為空字串
    this.setState({ input: "" });
  };

  render() {
    return (
      <div>
        <input
          onChange={e => this.updateInput(e.target.value)}
          value={this.state.input}
        />
        <button className="add-todo" onClick={this.handleAddTodo}>
          Add Todo
        </button>
      </div>
    );
  }
}

export default connect(
  null,
  { addTodo }
)(AddTodo);

現在我們的<AddTodo/>已經連線到了store。當我們增加一個新的todo時,該元件就能夠分發action從而改變store。我們現在還不能看到這個效果,因為別的元件尚未連線到store。如果你安裝了Redux DevTools谷歌瀏覽器擴充套件程式,那麼你可以看到action已經被分發了:

你也能夠看到store相應地發生了變化。

<TodoList/>元件負責渲染todos列表。因此,他需要從store中讀取資料。我們通過呼叫connect方法,並向其中傳入mapStateToProps引數從而提供給元件所需要的部分來自store資料。

我們的<Todo/>元件接收一個todo項作為prop。我們從todosbtIds物件獲取到所需資訊。然而,我們也需要store中的allIds欄位的資訊,以便指明哪些todos以哪種順序渲染。我們的mapStateToProps方法可能長這個樣子:

// components/TodoList.js

// ...other imports
import { connect } from "react-redux";

const TodoList = // ... UI component implementation

const mapStateToProps = state => {
  const { byIds, allIds } = state.todos || {};
  const todos =
    allIds && allIds.length
      ? allIds.map(id => (byIds ? { ...byIds[id], id } : null))
      : null;
  return { todos };
};

export default connect(mapStateToProps)(TodoList);

幸好我們有一個selector專門做這個事情,我們只需要簡單地匯入selector並且使用它。

// redux/selectors.js

export const getTodosState = store => store.todos;

export const getTodoList = store =>
  getTodosState(store) ? getTodosState(store).allIds : [];

export const getTodoById = (store, id) =>
  getTodosState(store) ? { ...getTodosState(store).byIds[id], id } : {};

export const getTodos = store =>
  getTodoList(store).map(id => getTodoById(store, id));
// components/TodoList.js

// ...other imports
import { connect } from "react-redux";
import { getTodos } from "../redux/selectors";

const TodoList = // ... UI component implementation

export default connect(state => ({ todos: getTodos(state) }))(TodoList);

既然我們的<TodoList/>也已經連線到了store。它應該能夠接收到todos列表了,遍歷他們,然後把每一個傳遞給<Todo/>元件。<Todo/>元件會將它們渲染到瀏覽器中,現在嘗試增加一個todo待辦項。它應該能立即出現在我們的todo列表中!

https://i.imgur.com/N68xvrG.png

我們接下來會connect更多的元件。在開始之前,讓我們先暫停一下,首先學習更多關於connect的知識。

常見的呼叫connect的方式

有不同的方式來呼叫connect,這取決於你現在編寫的元件型別。現對常用的方式進行總結:

不訂閱Store 訂閱Store
不注入Action Creators connect()(Component) connect(mapStateToProps)(Component)
注入Action Creators connect(null, mapDispatchToProps)(Component) connect(mapStateToProps, mapDispatchToProps)(Component)

不訂閱store並且不注入action建立函式

如果你呼叫connect方法並且不傳入任何引數,那麼你的元件將會:

  • store改變時不能夠重新渲染
  • 接收一個props.dispatch方法以便你手動分發actions
// ... Component
export default connect()(Component); // 元件將接收 `dispatch` (正如 <TodoList />!)

訂閱store但不注入action建立函式

如果你呼叫connect方法並且只傳入了mapStateToProps方法,你的元件將會:

  • 訂閱mapStateToProps從store中提取的部分值,當這些值改變時會重新渲染
  • 接收一個props.dispatch以便你手動分發actions
// ... Component
const mapStateToProps = state => state.partOfState;
export default connect(mapStateToProps)(Component);

不訂閱store但注入action建立函式

如果你呼叫connect方法並只傳入mapDispatchToProps引數,你的元件將會:

  • store改變時不重新渲染
  • props形式接收每個你通過mapDispatchToProps注入的action建立函式,能夠在你呼叫後自動分發actions
import { addTodo } from "./actionCreators";
// ... Component
export default connect(
  null,
  { addTodo }
)(Component);

訂閱store並且注入action建立函式

如果你在connect方法中傳入了mapStateToPropsmapDispatchToProps,你的元件將會:

  • 訂閱mapStateToProps從store中提取的部分值,當這些值改變時會重新渲染
  • props形式接收每個你通過mapDispatchToProps注入的action建立函式,能夠在你呼叫後自動分發actions
import * as actionCreators from "./actionCreators";
// ... Component
const mapStateToProps = state => state.partOfState;
export default connect(
  mapStateToProps,
  actionCreators
)(Component);

上面這四個例子基本覆蓋了所有connect的用法。如果想了解更多關於connnect的資訊,可以繼續閱讀[API 部分]()內容

現在我們把<TodoApp/>餘下的部分連線到store

我們該如何實現切換todos的操作呢?一個聰明的讀者也許已經有答案了。如果你截止目前的所有步驟都是緊跟指導完成的,現在是一個拋開指南、自己實現該功能的絕佳時機。不出所料,我們以類似的方法連線<Todo/>以分發toggleTodo

// components/Todo.js

// ... other imports
import { connect } from "react-redux";
import { toggleTodo } from "../redux/actions";

const Todo = // ... 實現元件

export default connect(
  null,
  { toggleTodo }
)(Todo);

現在我們的todo能夠切換為complete狀態了。馬上就好了!

https://i.imgur.com/4UBXYtj.png

終於,讓我們開始實現VisibilityFilters功能。

<VisiblityFilterse/>元件需要從store中讀取當前選中的過濾條件,並且分發actions。因此,我們需要把mapStateToProps以及mapDispatchToProps都傳遞給connect方法。mapStateToProps能夠作為visiblityFilter狀態的一個簡單的訪問器。mapDispatchToProps會包括setFilteraction建立函式。

// components/VisibilityFilters.js

// ... other imports
import { connect } from "react-redux";
import { setFilter } from "../redux/actions";

const VisibilityFilters = // ... 元件實現

const mapStateToProps = state => {
  return { activeFilter: state.visibilityFilter };
};
export default connect(
  mapStateToProps,
  { setFilter }
)(VisibilityFilters);

同時,我們也要更新我們的<TodoList/>元件來根據篩選條件過濾todos。先前我們傳遞給<TodoList/> connect 方法的mapStateToProps正如一個簡單的選擇了所有列表中的todos的selector。現在我們來寫一個selector以通過todos的狀態來進行篩選。

// redux/selectors.js

// ... other selectors
export const getTodosByVisibilityFilter = (store, visibilityFilter) => {
  const allTodos = getTodos(store);
  switch (visibilityFilter) {
    case VISIBILITY_FILTERS.COMPLETED:
      return allTodos.filter(todo => todo.completed);
    case VISIBILITY_FILTERS.INCOMPLETE:
      return allTodos.filter(todo => !todo.completed);
    case VISIBILITY_FILTERS.ALL:
    default:
      return allTodos;
  }
};

然後藉助selector連線到store

// components/TodoList.js

// ...

const mapStateToProps = state => {
  const { visibilityFilter } = state;
  const todos = getTodosByVisibilityFilter(state, visibilityFilter);
  return { todos };
};

export default connect(mapStateToProps)(TodoList);

現在我們已經用React-Redux完成了一個很簡單的todo app案例。我們所有的元件都已經連線到了store。是不是很棒呢?