react中使用redux
-
redux不是facebook团队开发的,不是只有react才可以用,angla、vue也可以用,不过vue中有专业的状态管理插件,vuex。vue3也出了一个pina。vue中并不用redux。
使用redux的原则是。能不用就不用,除非,不用比用更费劲。
react components : react组件
action creators:动作 创建者
dispatch: 派发
action: 是一个对象,包含 类型、数据
store: 存储
resucers: 加工厂
previous state: 之前的状态
new state:新状态
getState:获得状态
redux工作流程:
组件发出信息,要做些什么?传递给 动作创建者(action creator),动作创建者 创建好一个action对象(type:动作类型, data:执行动作需要的数据)派发给 老板(store),store 吩咐 reducer(加工者),加工者 把之前的状态 加工成新状态 返回给store(老板),组件向store中取状态。
用一个吃饭的例子举例:顾客(组件)对服务员说想吃饭,服务员列出一个单子:事件类型:吃饭,吃饭需要什么:蛋炒饭。给了老板(store),老板吩咐 厨师(reducer)做饭,厨师做完饭给老板,顾客从老板那里取饭。
求和案例纯react版本:
import React, { Component } from 'react' export default class Count extends Component { state = { count: 0 } // 加法 increment = () => { const { value } = this.selectNumber; const { count } = this.state; this.setState({count: count + value * 1}) } // 减法 decrement = () => { const { value } = this.selectNumber; const { count } = this.state; this.setState({ count: count - value * 1 }) } // 如果是奇数再加 incrementIfOdd = () => { const { value } = this.selectNumber; const { count } = this.state; if( count % 2 !== 0) { this.setState({ count: count + value * 1 }) } } // 异步加 incrementAsync = () => { const { value } = this.selectNumber; const { count } = this.state; setTimeout(() => { this.setState({ count: count + value * 1 }) }, 1000); } render() { const { count } = this.state; return ( <div> <h1>当前求和为: {count}</h1> <select ref={ c => this.selectNumber = c }> <option value="1">1</option> <option value="2">2</option> <option value="3">3</option> </select> <button onClick={this.increment}>+</button> <button onClick={this.decrement}>-</button> <button onClick={this.incrementIfOdd}>当前求和为奇数再加</button> <button onClick={this.incrementAsync}>异步加</button> </div> ) } }
redux api:
store.getState(): 获取store中存储的值
// redux只维护状态,但是不会触发页面更新(不会触发组件render的调用)
// 检测redux中状态的变化,就调用render store.subscribe(() => { this.setState({}); // 传入空对象,只为触发组件的render方法 });
上面是在单个组件内监听store的状态变化,有一个狠办法,在index.js入口文件监听store,重新渲染app组件,就不用在每个组件都监听store了
不用担心效率问题,react有diff算法,如果组件没有变化,不会更新所有的组件的,不会引起页面重绘重排。
import React from 'react' import ReactDOM from 'react-dom/client'; import App from './App.jsx' import store from './redux/store' const root = ReactDOM.createRoot(document.getElementById('root')); root.render(<App />); // 监听store中值的变化,更新App组件,就不用在每个组件中监听store掉用render了 store.subscribe(() => { root.render(<App />); })
先写一个精简版的redux, action creator 先省略,用一个简单的对象来替代它。 store全局就有一个, 每个组件都有自己的 reducer;好,开干
redux相关的都放在redux文件夹下,创建store.js、count_reducer.js
redux/store.js:
/** * 该文件专门用于暴露一个store对象 */ // 引入createStore,专门创建redux中最为核心的store对象 import { createStore } from 'redux' // 引入为Count组件服务的reducer import countReducer from './count_reducer' // 暴露store export default createStore(countReducer);
redux/count_reducer.js:
/** * 1.该文件是创建一个专门为Count组件服务的reducer,reducer的本质就是一个函数 * 2.reducer函数会接到两个参数,分别是:之前的状态(preState)、动作对象(action) */ // preState初始值是 undefined, 此时我们给他一个初始值 const initState = 0; export default function countReducer(preState = initState, action) { // 从action对象中获取 type、data const { type, data } = action; switch(type) { case 'increment': // 如果加 return preState + data; case 'decrement': // 如果减 return preState - data; default: return preState; } }
Count组件使用redux:
import React, { Component } from 'react' // 引入store,为了获取redux中保存的状态 import store from '../../redux/store' export default class Count extends Component { // componentDidMount() { // // 检测redux中状态的变化,就调用render // store.subscribe(() => { // this.setState({}); // 传入空对象,只为触发组件的render方法 // }); // } // 加法 increment = () => { const { value } = this.selectNumber; store.dispatch({ type: 'increment', data: value * 1 }) } // 减法 decrement = () => { const { value } = this.selectNumber; store.dispatch({ type: 'decrement', data: value * 1 }) } // 如果是奇数再加 incrementIfOdd = () => { const { value } = this.selectNumber; const count = store.getState(); if( count % 2 !== 0) { store.dispatch({ type: 'increment', data: value * 1 }) } } // 异步加 incrementAsync = () => { const { value } = this.selectNumber; setTimeout(() => { store.dispatch({ type: 'increment', data: value * 1 }) }, 1000); } render() { return ( <div> <h1>当前求和为: {store.getState()}</h1> <select ref={ c => this.selectNumber = c }> <option value="1">1</option> <option value="2">2</option> <option value="3">3</option> </select> <button onClick={this.increment}>+</button> <button onClick={this.decrement}>-</button> <button onClick={this.incrementIfOdd}>当前求和为奇数再加</button> <button onClick={this.incrementAsync}>异步加</button> </div> ) } }
完整版的redux例子:
上面的精简版的例子把actioncreator省去了,完整版的补回来,每个组件都有自己的actioncreator文件,专门为组件创建action对象。
在redux文件下给Count组件创建一个count_action.js文件:
/** * 改文件专门为Count组件生产action对象 */ export const createIncrementAction = data => ({ type: 'increment', data }); export const createDecrementAction = data => ({ type: 'decrement', data });
为什么不把type也当做参数传过来呢?因为后面还有异步actioncreator
那么在Count组件中就不要直接用对象替代actioncreator的工作了,要使用actioncreator创建action对象:
Count/index.jsx
import React, { Component } from 'react' // 引入store,为了获取redux中保存的状态 import store from '../../redux/store' // 引入actionCreator,专门用于创建action对象 import { createIncrementAction, createDecrementAction } from '../../redux/count_action' export default class Count extends Component { // componentDidMount() { // // 检测redux中状态的变化,就调用render // store.subscribe(() => { // this.setState({}); // 传入空对象,只为触发组件的render方法 // }); // } // 加法 increment = () => { const { value } = this.selectNumber; store.dispatch(createIncrementAction(value * 1)) } // 减法 decrement = () => { const { value } = this.selectNumber; store.dispatch(createDecrementAction(value * 1)) } // 如果是奇数再加 incrementIfOdd = () => { const { value } = this.selectNumber; const count = store.getState(); if( count % 2 !== 0) { store.dispatch(createIncrementAction(value * 1)) } } // 异步加 incrementAsync = () => { const { value } = this.selectNumber; setTimeout(() => { store.dispatch(createIncrementAction(value * 1)) }, 1000); } render() { return ( <div> <h1>当前求和为: {store.getState()}</h1> <select ref={ c => this.selectNumber = c }> <option value="1">1</option> <option value="2">2</option> <option value="3">3</option> </select> <button onClick={this.increment}>+</button> <button onClick={this.decrement}>-</button> <button onClick={this.incrementIfOdd}>当前求和为奇数再加</button> <button onClick={this.incrementAsync}>异步加</button> </div> ) } }
我们发现,action对象中的type在actioncreator中用到,在reducer中也用到了,那么最好把这个类型提取出来,防止某一个文件写错,导致程序无法正常工作。
在redux中创建一个constants.js文件,专门定义action中的type类型的常量值
constants.js:
/** * 改文件是专门定义action中type类型的常量值 */ export const INCREMENT = 'increment'; export const DECREMENT = 'decrement';
将reducer和actioncreator中的type类型用constants中的常量替换:
count_reducer.js:
/** * 1.该文件是创建一个专门为Count组件服务的reducer,reducer的本质就是一个函数 * 2.reducer函数会接到两个参数,分别是:之前的状态(preState)、动作对象(action) */ import { INCREMENT, DECREMENT } from './constants' // preState初始值是 undefined, 此时我们给他一个初始值 const initState = 0; export default function countReducer(preState = initState, action) { // 从action对象中获取 type、data const { type, data } = action; switch(type) { case INCREMENT: // 如果加 return preState + data; case DECREMENT: // 如果减 return preState - data; default: return preState; } }
count_action.js
/** * 改文件专门为Count组件生产action对象 */ import { INCREMENT, DECREMENT } from './constants' export const createIncrementAction = data => ({ type: INCREMENT, data }); export const createDecrementAction = data => ({ type: DECREMENT, data });
redux异步action:
返回值为函数的action就是异步action
下面把count组件的异步加用异步action写一下:
首先,异步action需要redux-thunk中间件的支持
安装redux-thunk
npm install redux-thunk
store.js
/** * 该文件专门用于暴露一个store对象 */ // 引入createStore,专门创建redux中最为核心的store对象 import { createStore, applyMiddleware } from 'redux' // 引入为Count组件服务的reducer import countReducer from './count_reducer' // 引入redux-thunk,用于支持异步action import thunk from 'redux-thunk' // 暴露store export default createStore(countReducer, applyMiddleware(thunk));
在count_action.js中写一个异步action
/** * 改文件专门为Count组件生产action对象 */ import { INCREMENT, DECREMENT } from './constants' // 同步action,就是指action的值为Object类型的一般对象 export const createIncrementAction = data => ({ type: INCREMENT, data }); export const createDecrementAction = data => ({ type: DECREMENT, data }); // 异步action,就是指action的值为函数,异步action中一般都会调用同步action,异步action不是必须要用的(组件中可以异步调用同步action) export const createIncrementAsyncAction = (data, time) => { return (dispatch) => { setTimeout(() => { dispatch(createIncrementAction(data)); },time) } }
在Count组件中使用:
import React, { Component } from 'react' // 引入store,为了获取redux中保存的状态 import store from '../../redux/store' // 引入actionCreator,专门用于创建action对象 import { createIncrementAction, createDecrementAction, createIncrementAsyncAction } from '../../redux/count_action' export default class Count extends Component { // componentDidMount() { // // 检测redux中状态的变化,就调用render // store.subscribe(() => { // this.setState({}); // 传入空对象,只为触发组件的render方法 // }); // } // 加法 increment = () => { const { value } = this.selectNumber; store.dispatch(createIncrementAction(value * 1)) } // 减法 decrement = () => { const { value } = this.selectNumber; store.dispatch(createDecrementAction(value * 1)) } // 如果是奇数再加 incrementIfOdd = () => { const { value } = this.selectNumber; const count = store.getState(); if( count % 2 !== 0) { store.dispatch(createIncrementAction(value * 1)) } } // 异步加 incrementAsync = () => { const { value } = this.selectNumber; // setTimeout(() => { store.dispatch(createIncrementAsyncAction(value * 1, 1000)) // }, 1000); } render() { return ( <div> <h1>当前求和为: {store.getState()}</h1> <select ref={ c => this.selectNumber = c }> <option value="1">1</option> <option value="2">2</option> <option value="3">3</option> </select> <button onClick={this.increment}>+</button> <button onClick={this.decrement}>-</button> <button onClick={this.incrementIfOdd}>当前求和为奇数再加</button> <button onClick={this.incrementAsync}>异步加</button> </div> ) } }
-