redux
Redux 是 JavaScript 状态容器,提供可预测化的状态管理。
如果你的应用有以下场景,可以考虑使用 Redux。
- 某个组件的状态,需要共享
- 某个状态需要在任何地方都可以拿到
- 一个组件需要改变全局状态
- 一个组件需要改变另一个组件的状态
redux 的基本概念
Store
Store 就是保存数据的地方。整个应用只能有一个 Store。
Store 有以下职责:
- 维持应用的 state;
- 提供 getState() 方法获取 state;
- 提供 dispatch(action) 方法更新 state;
- 通过 subscribe(listener) 注册监听器;
- 通过 subscribe(listener) 返回的函数注销监听器。
Redux 提供createStore这个函数,用来生成 Store。
1 | import { createStore } from 'redux'; |
2 | const store = createStore(fn); |
3 | const state = store.getState(); |
Action
Action 是把数据从应用传到 store 的有效载荷。它是 store 数据的唯一来源。
Action 本质上是 JavaScript 普通对象。其中的type属性是必须的,表示 Action 的名称。
1 | const action = { |
2 | type: 'ADD_TODO', |
3 | payload: 'Learn Redux' |
4 | }; |
Action 创建函数
Action 创建函数 就是生成 action 的方法。
1 | const ADD_TODO = "ADD_TODO" |
2 | function addTodo(text) { |
3 | return { |
4 | type: ADD_TODO, |
5 | text |
6 | } |
7 | } |
8 | const action = addTodo('Learn Redux'); |
Reducer
Reducer 指定了应用状态的变化如何响应 actions 并发送到 store 的,记住 actions 只是描述了有事情发生了这一事实,并没有描述应用如何更新 state。
Reducer 是一个函数,它接受 Action 和当前 State 作为参数,返回一个新的 State。
1 | const reducer = function (state, action) { |
2 | // ... |
3 | return new_state; |
4 | }; |
整个应用的初始状态,可以作为 State 的默认值。
1 | |
2 | const defaultState = 0; |
3 | const reducer = (state = defaultState, action) => { |
4 | switch (action.type) { |
5 | case 'ADD': |
6 | return state + action.payload; |
7 | default: |
8 | return state; |
9 | } |
10 | }; |
11 | |
12 | const state = reducer(0, { |
13 | type: 'ADD', |
14 | payload: 2 |
15 | }); |
实际应用中,Reducer 函数不用像上面这样手动调用,store.dispatch方法会触发 Reducer 的自动执行。
1 | import { createStore } from 'redux'; |
2 | const store = createStore(reducer); |
3 | |
4 | store.dispatch({ |
5 | type: 'ADD', |
6 | payload: 2 |
7 | }); |
Reducer 函数最重要的特征是,它是一个纯函数。
纯函数是函数式编程的概念,必须遵守以下一些约束。
- 不得改写参数
- 不能调用系统 I/O 的API
- 不能调用Date.now()或者Math.random()等不纯的方法,因为每次会得到不一样的结果
Reducer 函数里面不能改变 State,必须返回一个全新的对象。
1 | // State 是一个对象 |
2 | function reducer(state, action) { |
3 | return Object.assign({}, state, { newState }); |
4 | // 或者 |
5 | return { ...state, ...newState }; |
6 | } |
7 | |
8 | // State 是一个数组 |
9 | function reducer(state, action) { |
10 | return [...state, newItem]; |
11 | } |
Store 允许使用store.subscribe方法设置监听函数,一旦 State 发生变化,就自动执行这个函数。
store.subscribe方法返回一个函数,调用这个函数就可以解除监听。
1 | import { createStore } from 'redux'; |
2 | const store = createStore(reducer); |
3 | |
4 | let unsubscribe = store.subscribe(()=>{ |
5 | console.log(store.getState()) |
6 | }); |
7 | unsubscribe() |
Reducer 的拆分
Redux 提供了一个combineReducers方法,用于 Reducer 的拆分。
1 | import { combineReducers } from 'redux'; |
2 | |
3 | const todoApp = combineReducers({ |
4 | visibilityFilter, |
5 | todos |
6 | }) |
7 | |
8 | export default todoApp; |
等价于:
1 | export default function todoApp(state = {}, action) { |
2 | return { |
3 | visibilityFilter: visibilityFilter(state.visibilityFilter, action), |
4 | todos: todos(state.todos, action) |
5 | } |
6 | } |
简单实例
1 | import React from "react" |
2 | import ReactDOM from "react-dom" |
3 | import App from "./App" |
4 | |
5 | import { createStore } from "redux" |
6 | import reducer from "./reducers/counter" |
7 | |
8 | const store = createStore(reducer) |
9 | |
10 | const render = () => { |
11 | ReactDOM.render( |
12 | <App |
13 | onIncrement={() => store.dispatch({ type: "INCREMENT" })} |
14 | onDecrement={() => store.dispatch({ type: "DECREMENT" })} |
15 | value={store.getState()} |
16 | />, |
17 | document.getElementById("root") |
18 | ) |
19 | } |
20 | |
21 | render() |
22 | |
23 | store.subscribe(render) |
app.js
1 | import React from "react" |
2 | class App extends React.Component { |
3 | render() { |
4 | return ( |
5 | <div className='App'> |
6 | <p>{this.props.value}</p> |
7 | <button onClick={this.props.onIncrement}>+1</button> |
8 | <button onClick={this.props.onDecrement}>-1</button> |
9 | </div> |
10 | ) |
11 | } |
12 | } |
13 | |
14 | export default App |
counter.js
1 | const counter = (state = 0, action) => { |
2 | switch (action.type) { |
3 | case "INCREMENT": |
4 | return state + 1 |
5 | case "DECREMENT": |
6 | return state - 1 |
7 | default: |
8 | return state |
9 | } |
10 | } |
11 | |
12 | export default counter |
三大原则
单一数据源
整个应用的 state 被储存在一棵 object tree 中,并且这个 object tree 只存在于唯一一个 store 中。
State 是只读的
唯一改变 state 的方法就是触发 action,action 是一个用于描述已发生事件的普通对象。
使用纯函数来执行修改
为了描述 action 如何改变 state tree ,你需要编写 reducers。
中间件
使用中间件
1 | import { applyMiddleware, createStore } from 'redux'; |
2 | import logger from 'redux-logger'; |
3 | |
4 | const store = createStore( |
5 | reducer, |
6 | {}, |
7 | applyMiddleware(logger) |
8 | ); |
redux-thunk 中间件
redux-thunk 中间件处理异步操作,比如setTimout、网络请求等等。
1 | // index |
2 | import { createStore, applyMiddleware } from "redux"; |
3 | import thunk from "redux-thunk"; |
4 | import reducer from "./reducers"; |
5 | const store = createStore(reducer, {}, applyMiddleware(thunk)) |
6 | |
7 | // action |
8 | export function increment(num) { |
9 | return (dispatch) => { |
10 | setTimeout(() => { |
11 | dispatch({ |
12 | type: actions.INCREMENT, |
13 | num, |
14 | }) |
15 | }, 1000) |
16 | } |
17 | } |
react-redux
React-Redux 将所有组件分成两大类:UI 组件(presentational component)和容器组件(container component)。
UI 组件有以下几个特征:
- 只负责 UI 的呈现,不带有任何业务逻辑
- 没有状态(即不使用this.state这个变量)
- 所有数据都由参数(this.props)提供
- 不使用任何 Redux 的 API
容器组件的特征:
- 负责管理数据和业务逻辑,不负责 UI 的呈现
- 带有内部状态
- 使用 Redux 的 API
connect()
React-Redux 提供connect方法,用于从 UI 组件生成容器组件。
1 | import { connect } from 'react-redux' |
2 | const VisibleTodoList = connect(mapStateToProps,mapDispatchToProps)(TodoList); |
connect方法接受两个参数:mapStateToProps和mapDispatchToProps。它们定义了 UI 组件的业务逻辑。前者负责输入逻辑,即将state映射到 UI 组件的参数(props),后者负责输出逻辑,即将用户对 UI 组件的操作映射成 Action。
mapStateToProps()
mapStateToProps是一个函数。mapStateToProps执行后应该返回一个对象,里面的每一个键值对就是一个映射。
1 | const mapStateToProps = (state) => { |
2 | return { |
3 | todos: state.todos |
4 | } |
5 | } |
mapDispatchToProps()
mapDispatchToProps是connect函数的第二个参数,用来建立 UI 组件的参数到store.dispatch方法的映射。
1 | const mapDispatchToProps = ( |
2 | dispatch |
3 | ) => { |
4 | return { |
5 | onClick: () => { |
6 | dispatch({ |
7 | type: 'ADD_TODO' |
8 | }); |
9 | } |
10 | }; |
11 | } |
bindActionCreators 写法
1 | import * as counterActions from "./actions/counter" |
2 | import { bindActionCreators } from "redux" |
3 | |
4 | const mapDispatchToProps = (dispatch) => { |
5 | return { |
6 | counterActions: bindActionCreators(counterActions, dispatch), |
7 | } |
8 | } |
actions/counter.js
1 | export function increment(num) { |
2 | return { |
3 | type: "INCREMENT", |
4 | num, |
5 | } |
6 | } |
7 | |
8 | export function decrement(num) { |
9 | return { |
10 | type: "DECREMENT", |
11 | num, |
12 | } |
13 | } |
Provider 组件
connect 方法生成容器组件以后,需要让容器组件拿到state对象,才能生成 UI 组件的参数。
1 | ReactDOM.render( |
2 | <Provider store={store}> |
3 | <App /> |
4 | </Provider>, |
5 | document.getElementById("root") |
6 | ) |
简单实例
1 | import React from "react" |
2 | import ReactDOM from "react-dom" |
3 | import App from "./App" |
4 | |
5 | import { createStore } from "redux" |
6 | import reducer from "./reducers/counter" |
7 | import { Provider } from "react-redux" |
8 | |
9 | const store = createStore(reducer) |
10 | ReactDOM.render( |
11 | <Provider store={store}> |
12 | <App /> |
13 | </Provider>, |
14 | document.getElementById("root") |
15 | ) |
App.js
1 | import React from "react" |
2 | import { connect } from "react-redux" |
3 | import { increment, decrement } from "./actions/counter" |
4 | |
5 | class App extends React.Component { |
6 | render() { |
7 | const { count, increment, decrement } = this.props |
8 | return ( |
9 | <div className='App'> |
10 | <p>{count}</p> |
11 | <button onClick={increment}>+1</button> |
12 | <button onClick={decrement}>-1</button> |
13 | </div> |
14 | ) |
15 | } |
16 | } |
17 | |
18 | const mapStateToProps = (state) => { |
19 | return { |
20 | count: state.count, |
21 | } |
22 | } |
23 | |
24 | const mapDispatchToProps = (dispatch) => { |
25 | return { |
26 | increment: () => { |
27 | dispatch(increment()) |
28 | }, |
29 | decrement: () => { |
30 | dispatch(decrement()) |
31 | }, |
32 | } |
33 | } |
34 | export default connect(mapStateToProps, mapDispatchToProps)(App) |
actions/counter.js
1 | export function increment() { |
2 | return { |
3 | type: "INCREMENT", |
4 | } |
5 | } |
6 | |
7 | export function decrement() { |
8 | return { |
9 | type: "DECREMENT", |
10 | } |
11 | } |
reducers/counter.js
1 | const counter = (state = { count: 0 }, action) => { |
2 | switch (action.type) { |
3 | case "INCREMENT": |
4 | return { count: state.count + 1 } |
5 | case "DECREMENT": |
6 | return { count: state.count - 1 } |
7 | default: |
8 | return state |
9 | } |
10 | } |
11 | |
12 | export default counter |
redux 调试工具
1.chrome 浏览器安装插件:Redux DevTools
2.安装依赖
1 | npm install --save-dev redux-devtools-extension |
3.使用
1 | import { composeWithDevTools } from "redux-devtools-extension" |
2 | |
3 | const store = createStore( |
4 | rootReducer, |
5 | {}, |
6 | composeWithDevTools(applyMiddleware(logger, thunk)) |
7 | ) |