课程目标
- 掌握 redux 三大原则;
- 掌握 redux 基础使用;
- 掌握 react-redux 使用;
- 掌握 redux-thunk 使用。
内容
redux
- Redux 是一个独立的 JavaScript 状态管理库,不依赖于任何其它库;
- https://www.redux.org.cn。
安装
- npm i redux,或者 yarn add redux。
核心概念
理解 redux 几个核心概念与它们之间的关系:
state 状态
reducer 纯函数 - 提供修改状态的方法
- 相同的输入,永远返回相同的输出;
- 内部不能有任何副作用处理;
- 该函数结果只依赖自身参数,而不依赖任何外部数据。
store 仓库 - 管理状态:
- getState:获取当前状态;
- dispatch:发起一个 action;
- 修改 state,通过 dispatch 发起 action 之后,在 store 中,会调用 reducer 函数,并且会将 state 和 action 传递给 reducer,reducer 被调用之后,会返回新的 state;
- subscribe:
- 类似于 js 中的 addEventListener,当 store 的 state 发生改变时,就会触发 subscribe 中的监听函数;
- 会返回一个函数,当调用这个返回函数时会取消监听;
- replaceReducer:替换 reducer。
action 动作 - 对 state 的修改动作,action 本身是一个普通对象,该对象有 type(固定) 属性和 payload 属性
- action 属性是对 state 做出何种修改的描述;
- payload:附带参数。
state 对象
- 通常我们会把应用中的数据存储到一个对象树(Object Tree)中进行统一管理,我们把这个对象树称为:state;
state 是只读的
- 这里需要注意的是,为了保证数据状态的可维护和测试,不推荐直接修改 state 中的原数据,而是通过纯函数 reducer 修改 state。
action 对象
- 我们对 state 的修改是通过 reducer 纯函数来进行的,同时通过传入的 action 来执行具体的操作;
- action 是一个对象:
- type 属性:表示要进行操作的动作类型,增删改查...(type 名字固定);
- payload 属性:操作 state 的同时传入的数据。
- 这里需要注意的是,我们不直接去调用 reducer 函数,而是通过 store 对象提供的 dispatch 方法来调用。
store 对象
- 为了对 state,reducer,action 进行统一管理和维护,我们需要创建一个 store 对象。
redux 三大原则
- 单一数据源:整个应用的 state 被存储在一颗 Object Tree 中,并且这个 Object Tree 只存在于唯一一个 store 中;
- state 是只读的:唯一改变 state 的方法就是触发 action,action 是一个用于描述已发生事件的普通对象;
- 使用纯函数来执行修改。
import { createStore } from 'redux'; const fn = (state={ count: 1, num: 5 }, action) => { switch (action.type) { case 'addNum': return ({ ...state, num: state.num+1, }) case 'addCount': return ({ ...state, count: state.count+1, }) default: return state; } } const store = createStore(fn); store.dispatch({type: 'addNum'}); const state = store.getState(); console.log('store: ', state); const unSubscribe = store.subscribe(() => { console.log('subscribe: ', store.getState()) }); store.dispatch({type: 'addNum'}) store.dispatch({type: 'addNum'}) // unSubscribe(); store.replaceReducer((state={count: 10}, action) => { switch (action.type) { case 'addCount': return {...state, count: state.count+10} default: return state; } }) store.dispatch({type: 'addCount'})
redux API
createStore(reducer[,preloadedState],enhancer);
- reducer:function,接受两个参数,分别是当前的 state 树和要处理的 action,返回新的 state 树;
- preloadedState:初始的 state,在同构应用中,你可以决定是否把服务端传来的 state 传给它,或者从之前保存的用户会话中恢复一个传给它。如果你使用 combineReducers 创建 reducer,它必须是一个普通对象,与传入的 keys 保持同样的结构。否则,你可以自由传入任何 reducer 可理解的内容;
- enhancer:function,store enhancer 是一个组合 store creator 的高阶函数,返回一个新的强化过的 store creator。这与 middleware 相似,它也允许你通过复合函数改变 store 接口;
- 返回值(store):保存了应用所有 state 的对象,改变 state 的唯一方法是 dispatch action,你也可以 subscribe 监听 state 的变化,然后更新 UI。
reducer
- reducer(state,action);
store
- getState();
- dispatch(action);
- subscribe(listener);
- replaceReducer(nextReducer);
combineReducers(reducers) 将 reducer 函数拆分成多个单独的函数,拆分后的每个函数负责独立管理 state 的一部分;
applyMiddleware(...middlewares) 中间件。
react-redux
react 项目中的 redux 绑定库
- 安装:npm i react-redux;
- <Provider store>
// store.js // redux 不会给我们进行浅合并,所以需要将之前的 state 一并返还 import { createStore } from 'redux'; const count = (state={ count: 1, num: 5, todo: [], topics: {} }, action) => { switch (action.type) { case 'addCount': return { ...state, count: state.count + 1, } case 'addNum': return { ...state, num: state.num + 1, } } } const store = createStore(count); export default store;
// index.js import { Provider } from 'react-redux'; // 版本 7.0 之后增加了 hook import App from "./App"; import store from './store'; function ReduxTest() { return ( <Provider store={store}> <App/> </Provider> ); } export default ReduxTest;
- connect() —— 高阶函数:传入数据,返回一个函数;
- useDispatch 获取 dispatch;
- useStore 获取 store;
- useSelector 获取 state 中需要的数据。
// count.js /** * react-redux 中提供的解决方案 * - connect */ // const withConnect = connect(select: state => { // return { // count: state.count // } // }) // select 函数的作用:从 state 中截取该组件需要的部分,注意返回值是 state 中我们所需要的部分,注意类型必须是一个对象; // withConnect:高阶组件,调用该高阶组件,传入组件(Count)会被高阶组件调用返回一个新组件(NewCount),NewCount 被调用,Count 可以获取到截取后的 state,以及 dispatch 方法 import { useCallback } from 'react'; import { connect } from 'react-redux'; function Count(props) { const { count, dispatch } = props; const addCount = useCallback(() => { dispatch({type: 'addCount'}) },[]); return ( <div> <p>Count: {count}</p> <button onClick={addCount}>count-递增</button> </div> ); } // const withConnect = connect(state=>state); // const NewCount = withConnect(Count); // export default NewCount; export default connect(state=>state)(Count);
// num.js import { useCallback } from "react"; import { useDispatch, useSelector, useStore } from "react-redux"; /** * useDispatch 获取 dispatch; * useStore 获取 store; // 开发中用的较少 * useSelector 获取 state 中需要的数据。 */ function Num() { const { num } = useSelector(state => state) || {}; const dispatch = useDispatch(); const addNum = useCallback(() => { dispatch({type:'addNum'}); }, []); console.log('store: ', useStore()) return ( <div> <p>Num: {num}</p> <button onClick={addNum} >num-递增</button> </div> ); } export default Num;
// store.js // redux 不会给我们进行浅合并,所以需要将之前的 state 一并返还 import { createStore, combineReducers } from 'redux'; import count from './reducers/count'; import num from './reducers/num'; import todos from './reducers/todos'; import topics from './reducers/topics'; // function reducer(state={ // count: 1, // num: 5, // todos: [], // topics: {} // }, action) { // return { // count: count(state.count, action), // num: num(state.num, action), // todos: todos(state.todos, action), // topics: topics(state.topics, action) // } // } // 使用 combineReducers 实现 // const reducer = combineReducers({ // count, // num, // todos, // topics // }) // const store = createStore(reducer); // export default store; // 注意:action.type 要做到全局唯一 export default createStore(combineReducers({count, num, todos, topics}))
中间件
- 更新的过程中,去做一些其它的事情,dispatch ——> reducer 更新 state dispatch ——> 中间件 ——> reducer