# 简单使用
- react状态管理工具:react集中式管理状态工具
- 设计思想:
(1)Web 应用是一个状态机,视图与状态是一一对应的。
(2)所有的状态,保存在一个对象里面。
- 创建以及引用
store
- store常用的方法:
createStore
: (Reducer) => StoregetState
: () => Statedispatch
: (Action) => void
import { createStore } from 'redux';
const store = createStore(reducer);
1
2
2
// 使用数据
const state = store.getState();
1
2
2
dispatch
发射事件,接收一个action对象
store.dispatch({
type: 'ADD_TODO',
payload: 'Learn Redux' // 荷载参数
});
1
2
3
4
2
3
4
action
: view通知store发起数据改变
// 目标action格式
const action = {
type: 'ADD_TODO',
payload: 'Learn Redux' // 荷载参数
};
1
2
3
4
5
2
3
4
5
- 简化,写一个actionCreater,传入载荷,生成action对象
const ADD_TODO = '添加 TODO';
function addTodo(text) {
return {
type: ADD_TODO,
text
}
}
const action = addTodo('Learn Redux');
1
2
3
4
5
6
7
8
9
10
2
3
4
5
6
7
8
9
10
- reducer接收两个参数,当前state和action对象。返回新的state
const reducer = function (state, action) {
// ...
return new_state;
};
1
2
3
4
2
3
4
- 可以和数组的reduce进行结合
const actions = [
{ type: 'ADD', payload: 0 },
{ type: 'ADD', payload: 1 },
{ type: 'ADD', payload: 2 }
];
const total = actions.reduce(reducer, 0); // 3
1
2
3
4
5
6
7
2
3
4
5
6
7
- 纯函数
- 不得改写参数
- 不能调用系统 I/O 的API
- 不能调用Date.now()或者Math.random()等不纯的方法,因为每次会得到不一样的结果
- Reducer 函数里面不能改变 State,必须返回一个全新的对象,请参考下面的写法。
// State 是一个对象
function reducer(state, action) {
return Object.assign({}, state, { thingToChange });
// 或者
return { ...state, ...newState };
}
// State 是一个数组
function reducer(state, action) {
return [...state, newItem];
}
1
2
3
4
5
6
7
8
9
10
11
2
3
4
5
6
7
8
9
10
11
- state状态太多,可以进行拆分
const chatReducer = (state = defaultState, action = {}) => {
return {
chatLog: chatLog(state.chatLog, action),
statusMessage: statusMessage(state.statusMessage, action),
userName: userName(state.userName, action)
}
};
// 每一个属性,又对应一个更小的 reducer,这样整体的reducer就会更清晰
1
2
3
4
5
6
7
8
2
3
4
5
6
7
8
state是一个对象,有chatLog、statusMessage、userName等属性,然后属性值又是一个个更小的reducer函数的返回值,每次调用整体的reducer,就会调用重新计算计算更小的reducer,同时整个对象又作为整体新state返回,更新store
- 另一种拆分方式,通过
combineReducers
将多个reducer进行合并,作为整体reducer输出
import { combineReducers } from 'redux';
const chatReducer = combineReducers({
chatLog,
statusMessage,
userName
})
export default todoApp;
1
2
3
4
5
6
7
8
9
2
3
4
5
6
7
8
9
- State 的属性名必须与子 Reducer 同名。如果不同名,就要采用下面的写法。
const reducer = combineReducers({
a: doSomethingWithA,
b: processB,
c: c
})
1
2
3
4
5
2
3
4
5
function listerner() {
let newState = store.getState();
component.setState(newState);
}
// 设置监听函数
store.subscribe(listener);
1
2
3
4
5
6
7
2
3
4
5
6
7
- 或者直接在根组件
const render = () => ReactDOM.render( <App />, document.getElementById("root") );
render();
store.subscribe(render);
1
2
3
4
2
3
4
- store定义
import { createStore } from 'redux';
const ADD_ONE = '+1';
const DEL_ONE = '-1';
const incre = (num = 1) => ({ type: ADD_ONE, num })
const decre = (num = 1) => ({ type: DEL_ONE, num })
function countReducer(state = 0, action) {
const { type, num } = action;
switch (type) {
case ADD_ONE: return state + num ?? 1;
case DEL_ONE: return state - num ?? 1;
default: return state
}
}
const store = createStore(countReducer);
// 导出actionCreator和store
export { incre, decre }
export default store;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
- 组件使用
let Test = () => (
<>
<div>{store.getState()}</div>
<button onClick={() => store.dispatch(decre())}>-1</button>
<button onClick={() => store.dispatch(incre())}>+1</button>
</>
)
1
2
3
4
5
6
7
2
3
4
5
6
7
- 监听改变刷新
const render = () => ReactDOM.render(<App />, document.getElementById("root"));
render();
store.subscribe(render);
1
2
3
4
2
3
4
# 异步中间件
- 普通操作只能同步修改状态,要异步修改,就要用到
异步中间件
:applyMiddleware
import { applyMiddleware, createStore } from 'redux';
import { createLogger } from 'redux-logger'; // 打印日志的插件
const logger = createLogger();
const store = createStore(
reducer,
applyMiddleware(logger)
);
1
2
3
4
5
6
7
8
9
2
3
4
5
6
7
8
9
- createStore方法可以接受整个应用的初始状态作为参数,那样的话,applyMiddleware就是第三个参数了。
const store = createStore(
reducer,
initial_state,
applyMiddleware(logger)
);
1
2
3
4
5
2
3
4
5
- 多个中间件【可能有顺序要求】
const store = createStore(
reducer,
applyMiddleware(thunk, promise, logger)
);
1
2
3
4
2
3
4
export default function applyMiddleware(...middlewares) {
return (createStore) => (reducer, preloadedState, enhancer) => {
var store = createStore(reducer, preloadedState, enhancer);
var dispatch = store.dispatch;
var chain = [];
var middlewareAPI = {
getState: store.getState,
dispatch: (action) => dispatch(action)
};
chain = middlewares.map(middleware => middleware(middlewareAPI));
dispatch = compose(...chain)(store.dispatch);
return {...store, dispatch}
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
- 异步操作要发起三个种action:
- 操作发起时的 Action
- 操作成功时的 Action
- 操作失败时的 Action
// 写法一:名称相同,参数不同
{ type: 'FETCH_POSTS' }
{ type: 'FETCH_POSTS', status: 'error', error: 'Oops' }
{ type: 'FETCH_POSTS', status: 'success', response: { ... } }
// 写法二:名称不同
{ type: 'FETCH_POSTS_REQUEST' }
{ type: 'FETCH_POSTS_FAILURE', error: 'Oops' }
{ type: 'FETCH_POSTS_SUCCESS', response: { ... } }
1
2
3
4
5
6
7
8
9
2
3
4
5
6
7
8
9
- 思路
- 操作开始时,送出一个 Action,触发 State 更新为"正在操作"状态,View 重新渲染
- 操作结束后,再送出一个 Action,触发 State 更新为"操作结束"状态,View 再一次重新渲染
产生问题:
store.dispatch
只能接收action
作为参数,不是函数,所以需要用redux-thunk
- 作用:让store.dispatch可以接收函数作为参数
store接受到这个参数发现是函数以后会去执行这个函数。
import { createStore, applyMiddleware } from 'redux';
import thunk from 'redux-thunk';
import reducer from './reducers';
// Note: this API requires redux@>=3.1.0
const store = createStore(
reducer,
applyMiddleware(thunk)
);
1
2
3
4
5
6
7
8
9
2
3
4
5
6
7
8
9
actionCreator
定义:actionCreator
返回的是正常的action对象
或一个函数
// actionCreator
const incre = (num = 1) => ({ type: ADD_ONE, num })
const decre = (num = 1) => ({ type: DEL_ONE, num })
const increAsync = (num, timer) => (dispatch, getState) =>
new Promise(resolve => setTimeout(() => resolve(), timer))
.then(res => dispatch({ type: ADD_ONE, num }))
1
2
3
4
5
6
2
3
4
5
6
- 组件使用
<button onClick={() => store.dispatch(decre())}>-1</button>
<button onClick={() => store.dispatch(incre())}>+1</button>
<button onClick={
() => store.dispatch(increAsync(100, 1000))
.then(() => console.log(store.getState()))
}>异步+100</button>
1
2
3
4
5
6
2
3
4
5
6
- 作用:让redux的dispatch接收函数,返回
Promise
对象,可以链式操作异步请求
import { createStore, applyMiddleware } from 'redux';
import promiseMiddleware from 'redux-promise';
import reducer from './reducers';
const store = createStore(
reducer,
applyMiddleware(promiseMiddleware)
);
1
2
3
4
5
6
7
8
2
3
4
5
6
7
8
const fetchPosts =
(dispatch, postTitle) => new Promise(function (resolve, reject) {
dispatch(requestPosts(postTitle));
return fetch(`/some/API/${postTitle}.json`)
.then(response => {
type: 'FETCH_POSTS',
payload: response.json()
});
});
1
2
3
4
5
6
7
8
9
2
3
4
5
6
7
8
9
- 使用
store.dispatch(fetchPosts('reactjs')).then(() =>
console.log(store.getState())
);
1
2
3
2
3
import { createAction } from 'redux-actions';
class AsyncApp extends Component {
componentDidMount() {
const { dispatch, selectedPost } = this.props
// 发出同步 Action
dispatch(requestPosts(selectedPost));
// 发出异步 Action
dispatch(createAction(
'FETCH_POSTS',
fetch(`/some/API/${postTitle}.json`)
.then(response => response.json())
));
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
2
3
4
5
6
7
8
9
10
11
12
13
14
- 第二个dispatch方法发出的是异步 Action,只有等到操作结束,这个 Action 才会实际发出。注意,createAction的第二个参数必须是一个 Promise 对象。
# react-redux
- react-redux是专门为react将redux进行封装。
- UI组件不能读取或设置redux状态,要通过容器组件和redux进行连接,连接的方式是props,容器组件由connect生成
import { incre, decre, increAsync } from "../store"; // 引入 actionCreator
import { connect } from 'react-redux';
// UI组件
let TestUI = (props) => (
<>
<div>{props.count}</div>
<button onClick={props.decre}>-1</button>
<button onClick={props.incre}>+1</button>
<button onClick={props.increAsync}>异步+100</button>
</>
)
// store 里面的 state 映射成容器组件的 props
const mapStateToProps = state => ({
count: state
})
// 将dispatch映射成容器组件的props
const mapDispatchToProps = dispatch => ({
incre: () => dispatch(incre()),
decre: () => dispatch(decre()),
increAsync: () => dispatch(increAsync(100, 1000)),
})
// 用UI组件以及store的规则创建《容器组件》
const Test = connect(mapStateToProps, mapDispatchToProps)(TestUI)
export default Test;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
mapDispatchToProps
可以简写为对象形式,值为actionCreator或函数,调用的返回值会被dispatch
调用
// 不同的actionCreator
const incre = (num = 1) => ({ type: ADD_ONE, num })
const decre = (num = 1) => ({ type: DEL_ONE, num })
const increAsync = (num = 100, timer = 1000) => (dispatch) =>
new Promise(resolve => setTimeout(() => resolve(), timer))
.then(res => dispatch({ type: ADD_ONE, num }))
1
2
3
4
5
6
2
3
4
5
6
// mapDispatchToProps 对象形式简写
const mapDispatchToProps = {
incre,
decre,
increAsync, // 返回的是函数,但是也会被dispatch调用,内部又因为有 redux-thunk,所以可以被正确解析
}
// 用UI组件以及store的规则创建《容器组件》
const Test = connect(mapStateToProps, mapDispatchToProps)(TestUI)
1
2
3
4
5
6
7
8
9
2
3
4
5
6
7
8
9
- UI组件调用
<>
<div>{props.count}</div>
<button onClick={() => props.decre(1)}>-1</button>
<button onClick={() => props.incre(1)}>+1</button>
<button onClick={() => props.increAsync(100, 1000)}>异步+100</button>
</>
1
2
3
4
5
6
2
3
4
5
6
import { connect } from 'react-redux'
import { incre, decre, increAsync } from '../store/action-creator'
import React from 'react';
const Computer = (props) => (
<div>
<div>{props.count}</div>
<button onClick={() => props.decre(1)}>-1</button>
<button onClick={() => props.incre(1)}>+1</button>
<button onClick={() => props.increAsync(11, 1000)}>异步+11</button>
</div>
)
export default connect(
state => ({ count: state }),
{
incre,
decre,
increAsync
}
)(Computer)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
- 顶层通过
Provider
组件传入store对象- 每个通过connect生成的容器组件的props都会被注入
mapStateToProps
/mapDispatchToProps
定义的store - store更新,内部直接调用组件render,不用再用
subscribe
监听了
- 每个通过connect生成的容器组件的props都会被注入
// index.js
import React from "react";
import ReactDOM from "react-dom";
import App from './app';
import { Provider } from 'react-redux';
import store from './store';
ReactDOM.render(
<Provider store={store}>
<App />
</Provider>,
document.getElementById("root")
)
1
2
3
4
5
6
7
8
9
10
11
12
13
2
3
4
5
6
7
8
9
10
11
12
13
- 如果有多个reducer,就需要用
combineReducers
进行合并,再进行store创建
const reducers = combineReducers({ count, person });
const store = createStore(reducers, applyMiddleware(thunk));
1
2
2
组件取状态就要对应的取
// 容器组件创建
export default connect(
state => ({ count: state.count }),
{
incre,
decre,
increAsync
}
)(Computer)
1
2
3
4
5
6
7
8
9
2
3
4
5
6
7
8
9
注:即使是多个组件,一个
dispatch
还是会触发所有的reducer
,所以action的type不能重名
- 将reducers集中管理,actions集中管理
src
|-redux
| |-reducers
| | |-person.js
| | |-count.js
| | |-index.js
| |-actions
| | |-person.js
| | |-count.js
| |-constans.js
| |-index.js
|-components
|-app.js
|-index.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
2
3
4
5
6
7
8
9
10
11
12
13
14
redux/constans.js
集中定义常量,actions-type
const INCRE = '+1';
const DECRE = '-1';
const INCRE_ASYNC = '异步+1';
const ADD_PERSON = '加一个人';
export {
INCRE,
DECRE,
INCRE_ASYNC,
ADD_PERSON,
}
1
2
3
4
5
6
7
8
9
10
11
12
2
3
4
5
6
7
8
9
10
11
12
redux/reducers/index.js
集中combineReducers
,这样store目录更清晰
import count from './count';
import person from './person';
import { combineReducers } from 'redux';
export default combineReducers({
count,
person
});
1
2
3
4
5
6
7
8
2
3
4
5
6
7
8
redux/index.js
开发中尽量不做改动
import { createStore, applyMiddleware } from 'redux';
import { composeWithDevTools } from 'redux-devtools-extension';
import reducers from './reducers';
import thunk from 'redux-thunk';
const store = createStore(reducers,
composeWithDevTools(applyMiddleware(thunk))
);
export default store;
1
2
3
4
5
6
7
8
9
10
2
3
4
5
6
7
8
9
10
# redux-devtools
- 谷歌商店安装/解压包安装
- 项目安装库
npm i redux-devtools-extension
1
// redux/index.js
import { createStore, applyMiddleware } from 'redux';
import { composeWithDevTools } from '@redux-devtools/extension';
const store = createStore(
reducer,
composeWithDevTools(
applyMiddleware(...middleware)
// other store enhancers if any
)
);
1
2
3
4
5
6
7
8
9
10
11
2
3
4
5
6
7
8
9
10
11