React-Redux 中文文件
介紹
快速開始
React-Redux
是Redux
的官方React
繫結庫。它能夠使你的React
元件從Redux store
中讀取資料,並且向store
分發actions
以更新資料
安裝
在你的React app中使用React-Redux:
npm install --save react-redux
或者
yarn add react-redux
Provider
和connect
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 list
app
一個Todo List例項
跳轉到:
React UI 元件
我們所用到的React UI
元件如下:
-
TodoApp
:我們應用的入口元件,它render
出AddTodo
,TodoList
和VisibilityFilters
元件 -
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
名為setFilter
的action
以更新已選過濾條件
- 從父元件接收一個
-
constants
:儲存我們的app所有需要的常量資料 - 最後,
index
將app渲染到DOM中
the Redux Store
-
Store:
-
todos
:標準化的todos的reducer
。包含了byIds
的待辦項map
物件結構,和一個包含了所有待辦項id的allIds
陣列 -
visibilityFilters
:簡單的字串all
,completed
, orincomplete
.
-
-
Action Creators:
-
addTodo
:建立增添待辦項的action
。接收一個string
變數content
,返回ADD_TODO
型別的action,以及一個payload
物件(包含了自增的id
和content
屬性) -
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_FILTER
action 時負責更新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-Redux
將store
連線到我們的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/>
現在擁有了一個prop
:addTodo
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
。我們從todos
的btIds
物件獲取到所需資訊。然而,我們也需要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
列表中!
我們接下來會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
方法中傳入了mapStateToProps
和mapDispatchToProps
,你的元件將會:
- 訂閱
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
狀態了。馬上就好了!
終於,讓我們開始實現VisibilityFilters
功能。
<VisiblityFilterse/>
元件需要從store中讀取當前選中的過濾條件,並且分發actions
。因此,我們需要把mapStateToProps
以及mapDispatchToProps
都傳遞給connect
方法。mapStateToProps
能夠作為visiblityFilter
狀態的一個簡單的訪問器。mapDispatchToProps
會包括setFilter
action建立函式。
// 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。是不是很棒呢?