组件嵌套多层时,可能需要把父级的状态一层一层向下传递,这样在管理和使用上极其不便。
Redux 是 JS 的状态容器,提供可预测化的状态管理。在 React 中使用 Redux,可以把所有的 state 集中到组件顶部,能灵活地将所有 state 分发给所有的组件。
3.1 Redux 使用
Redux 本身不依赖于任何库,可以与任何 UI 层框架搭配使用,大小也只有 2KB。
核心概念
1)store:一个数据容器,用来管理和保存项目的 state,整个应用只能有一个 store。
2)state:一个对象,在 state 中存储相应的数据,需要时通过 store 提供的方法获取。
3)action:一个通知命令,视图层发起的一个操作,对 state 进行修改。
3.1.1 action、createStore 和 reducer 函数
action 本质上是一个 JS 普通对象,必须包含一个字符串类型的 type 属性,代表要对 state 做何种操作。最终 action 通过 store 传入 reducer 函数,完成对 state的修改。
Redux Toolkit 是官方推荐的用于编写 Redux 逻辑的方式,它包含了 Redux Core,还包含了对创建 Redux 应用非常必要的包和功能。
安装 Redux Core:npm install redux
安装 Redux Toolkit:npm install @reduxjs/toolkit
先看 Redux 中的核心方法 createStore,用于创建项目中的 store。
1 /** 2 * This is a reducer - a function that takes a current state value and an 3 * action object describing "what happened", and returns a new state value. 4 * A reducer's function signature is: (state, action) => newState 5 * 6 * The Redux state should contain only plain JS objects, arrays, and primitives. 7 * The root state value is usually an object. It's important that you should 8 * not mutate the state object, but return a new object if the state changes. 9 * 10 * You can use any conditional logic you want in a reducer. In this example, 11 * we use a switch statement, but it's not required. 12 */ 13 function counterReducer(state = { value: 0 }, action) { 14 switch (action.type) { 15 case 'counter/incremented': 16 return { value: state.value + 1 } 17 case 'counter/decremented': 18 return { value: state.value - 1 } 19 default: 20 return state 21 } 22 } 23 24 // Create a Redux store holding the state of your app. 25 // Its API is { subscribe, dispatch, getState }. 26 let store = createStore(counterReducer)
createStore 创建 store 时需要传入 reducer 函数。reducer 是一个函数,它接受当前状态值和描述“发生了什么”的操作对象,并返回一个新的状态值。
在 Redux 中,所有的数据都会保存在同一个 state 对象中。
3.1.2 store
store 的三个常用方法。
1)getState:用于获取 state。
2)dispatch(action):发起一个 action。
3)subscribe(listener):注册一个监听器监听 state 发生的变化。返回一个注销监听器的方法,用于取消监听器。
Redux 基本流程:基于 reducer 创建 store,从 store 中获取 state 传递给视图,当视图被操作时,通过 dispatch 发起一个 action,store 接到 action 后,把 state 和 action 传递给 reducer,reducer 更新 state 并把新的 state 传给视图进行更新。
下面是纯 Redux 的测试代码:
1 <!DOCTYPE html> 2 <html> 3 <head> 4 <title>React Demo</title> 5 <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.2.3/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-rbsA2VBKQhggwzxH7pPCaAqO46MgnOM80zW1RWuH61DGLwZJEdK2Kadq2F9CUG65" crossorigin="anonymous"> 6 <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.2.3/dist/js/bootstrap.bundle.min.js" integrity="sha384-kenU1KFdBIe4zVF0s0G1M5b4hcpxyD9F7jL+jjXkk+Q2h455rYXK/7HAuoJl+0I4" crossorigin="anonymous"></script> 7 <script src="https://cdnjs.cloudflare.com/ajax/libs/redux/4.2.0/redux.min.js" crossorigin></script> 8 </head> 9 <body> 10 <div> 11 <p> 12 <span id="colorEL">Watch my color.</span> 13 <button id="red">RED</button> 14 <button id="green">GREEN</button> 15 <button id="toggle">TOGGLE</button> 16 </p> 17 </div> 18 <script> 19 var initialState = { color: 'red' }; 20 21 // Reducer 22 function colorReducer(state, action){ 23 if(typeof state == 'undefined'){ 24 console.log('return initialState'); 25 return initialState; 26 } 27 28 switch(action.type){ 29 case 'RED': 30 return { color: 'red' }; 31 case 'GREEN': 32 return { color: 'green' }; 33 case 'TOGGLE': 34 return state.color == 'red' ? { color: 'green' } : { color: 'red' }; 35 default: 36 return state; 37 } 38 } 39 40 // create store by the reducer 41 var myStore = Redux.createStore(colorReducer); 42 var colorEL = document.getElementById('colorEL'); 43 44 function render(){ 45 colorEL.style.color = myStore.getState().color; 46 } 47 48 render(); 49 50 myStore.subscribe(render); 51 52 document.getElementById('red').addEventListener('click', function(){ 53 myStore.dispatch({ type: 'RED' }); 54 }); 55 56 document.getElementById('green').addEventListener('click', function(){ 57 myStore.dispatch({ type: 'GREEN' }); 58 }); 59 60 document.getElementById('toggle').addEventListener('click', function(){ 61 myStore.dispatch({ type: 'TOGGLE' }); 62 }); 63 </script> 64 </body> 65 </html>
3.2 React-Redux
在 React 中直接使用 Redux 特别麻烦,需要一层一层传递 store。
Redux 官方提供了一个 React 绑定库 - React-Redux。
3.2.1 安装与配置
命令:npm install react-redux,需要注意的是,React-Redux 是 Redux 的官方针对 React 开发的扩展库,默认没有在 React 项目中安装,需要手动来安装。React-Redux 是依赖于redux,所以 Redux 也 必须安装。
安装了 React-Redux 之后,就可以把数据和视图进行分离。
1)第一步,创建 store。
2)通过 React-Redux 把 store 关联到项目中。
React-Redux 提供了组件 Provider,它的作用是向后代子孙传递信息。Provider 应该在整个项目的最外层。
3.2.2 connect
React-Redux 提供了 connect 方法用于接收 Provider 传递下来的 store 中的 state 和 dispatch。
下面是一个使用了 connect 的例子。
1 <!DOCTYPE html> 2 <html> 3 <head> 4 <title>React Demo</title> 5 <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.2.3/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-rbsA2VBKQhggwzxH7pPCaAqO46MgnOM80zW1RWuH61DGLwZJEdK2Kadq2F9CUG65" crossorigin="anonymous"> 6 <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.2.3/dist/js/bootstrap.bundle.min.js" integrity="sha384-kenU1KFdBIe4zVF0s0G1M5b4hcpxyD9F7jL+jjXkk+Q2h455rYXK/7HAuoJl+0I4" crossorigin="anonymous"></script> 7 <script src="https://unpkg.com/react@18/umd/react.development.js" crossorigin></script> 8 <script src="https://unpkg.com/react-dom@18/umd/react-dom.development.js" crossorigin></script> 9 <script src="https://cdnjs.cloudflare.com/ajax/libs/redux/4.2.0/redux.min.js" crossorigin></script> 10 <script src="https://cdnjs.cloudflare.com/ajax/libs/react-redux/8.0.5/react-redux.min.js" crossorigin></script> 11 <script src="https://unpkg.com/babel-standalone@6/babel.min.js" crossorigin></script> 12 </head> 13 <body> 14 <div id="root"></div> 15 <script type="text/babel"> 16 // Reducer 17 function countFn(state = { 18 count: 1 19 }, action){ 20 switch(action.type){ 21 case "COUNT_PLUS": 22 return { count: state.count + 1}; 23 case "COUNT_REDUCE": 24 return { count: state.count - 1}; 25 } 26 27 return state; 28 } 29 30 // create store by the reducer 31 let store = Redux.createStore(countFn); 32 33 class App extends React.Component{ 34 render(){ 35 let {count, dispatch} = this.props; 36 37 return ( 38 <div> 39 <button onClick={() => { 40 dispatch({type: "COUNT_REDUCE"}); 41 }}>-</button> 42 <span>{count}</span> 43 <button onClick={() => { 44 dispatch({type: "COUNT_PLUS"}); 45 }}>+</button> 46 </div> 47 ); 48 } 49 } 50 51 App = ReactRedux.connect(state => state)(App); 52 53 ReactDOM.createRoot(document.querySelector('#root')) 54 .render(<ReactRedux.Provider store={store}> 55 <App /> 56 </ReactRedux.Provider>); 57 </script> 58 </body> 59 </html>
3.2.3 Hooks
React-Redux 7.X 新增了 Hooks。
1)const state = useSelector(state => state)
useSelector 接收一个回调函数,这个函数会被调用,并且 store 中的 state 会被传递给这个函数,返回值是想要的数据,必须有返回值。
2)const dispatch = useDispatch()
返回值为 Redux 的 dispatch 方法。
3) const store = useStore()
返回 Redux 的 store。
3.4 reducer 拆分与合并
combineReducers(rootReducer)。
3.5 redux-thunk 中间件
dispatch 一个 action 之后,到达 reducer 之前,进行一些额外的操作,就需要用到 middleware。你可以利用 Redux middleware 来进行日志记录、创建崩溃报告、调用异步接口或者路由等等。
换言之,中间件都是对 store.dispatch() 的增强。
redux-thunk 主要用于解决项目中的异步请求。
1)安装:npm install redux-thunk。
2)在创建 store 时,引入中间件。Redux 中提供了 applyMiddleware(...middlewares)方法,把方法的返回值传递给 createStore。
3)添加完成后可以利用 thunk 做中间处理。使用了 thunk 之后,dispatch 可以接收两种不同类型的参数。参数类型是对象时,不会经过中间件处理而是直接把 action 发送到 reducer。参数类型是函数时,会把 dispatch 和 getState 作为参数传递给该函数,在函数中进行中间处理,处理之后调用 dispatch 更新 state。