导读
redux、react-redux、action、reducer、store、容器组件、Provider、多组件数据共享
redux基础概念
原理图
只负责管理状态.不负责更新视图.
redux的三个核心概念
action
- (动作)值为对象(同步action)
- type:标识属性, 值为字符串, 唯一, 必要属性
- data:数据属性, 值类型任意, 可选属性
- 例子:{ type: ‘ADD_STUDENT’,data:{name:’wxl’,age:18} }
- 函数(异步action)
- 但是最终Store只接收Object的action,如果action为函数,那么Store会去执行这个函数,而不是传给Reducres
reducer
- 用于初始化状态、加工状态。
- 加工时,根据旧的state和action,产生新的state的纯函数。
store
- 将state、action、reducer联系在一起的对象
- 如何得到此对象
- import {createStore} from ‘redux’
-
import countReducer from ‘./count_reducer.js’
- const store = createStore(countReducer)
- 此对象的功能
- getState(): 得到state
- dispatch(action): 分发action, 触发reducer调用, 产生新的state
- subscribe(listener): 注册监听, 当产生了新的state时, 自动调用,可以在这里更新视图
示例
count组件
import React, { Component } from 'react' import store from '../../redux/store' //引入actionCreator 专门用于创建action对象 import { createIncrementAction , createDecrementAction , createIncrementAsyncAction } from '../../redux/count_action' export default class index extends Component { //加法 increment = ()=>{ const {value} = this.selectNumber //获取下拉框选中的值 //通知redux加上这个value // store.dispatch({type:'increment',data:parseInt(value)}) store.dispatch(createIncrementAction(parseInt(value))) } //减法 decrement = ()=>{ const {value} = this.selectNumber store.dispatch(createDecrementAction(parseInt(value))) } //store存的数为奇数再加 incrementIfOdd = ()=>{ const {value} = this.selectNumber const count = store.getState() if(count%2!==0){ store.dispatch(createIncrementAction(parseInt(value))) } } //异步加 incrementAsync = ()=>{ const {value} = this.selectNumber // 异步action也可以直接写在组件里,异步调用同步action,即使用对象方式 // setTimeout(()=>{ // store.dispatch(createIncrementAction(parseInt(value))) // },1000) //也可以真正使用异步action,不过需要redux-thunk store.dispatch(createIncrementAsyncAction(parseInt(value))) } render() { return ( <div> <h1>当前求和:{store.getState()}</h1> <select name="" id="" ref={(c) => (this.selectNumber = c)}> <option value="1">1</option> <option value="2">2</option> <option value="3">3</option> <option value="4">4</option> </select> <button onClick={this.increment}>+</button> <button onClick={this.decrement}>-</button> <button onClick={this.incrementIfOdd}>当前求和为奇数加</button> <button onClick={this.incrementAsync}>异步加</button> </div> ) } }
store.js
import {createStore , applyMiddleware} from 'redux' //引入为Count组件服务的reducer import countReducer from './count_reducer.js' //引入redux-thunk 用于支持异步action,需要applyMiddleware执行这个中间件 import thunk from 'redux-thunk' const store = createStore(countReducer,applyMiddleware(thunk)) export default store //整个应用只有一个store对象
常量映射表constant.js
// 该文件用于定义action对象中type类型的常量值 export const INCREMENT = 'increment' export const DECREMENT = 'decrement'
count_action.js
// 专门为count组件生成action对象 import { INCREMENT,DECREMENT } from "./constant" //同步action,action是个对象 export function createIncrementAction(data){ return {type:INCREMENT,data:data} } export function createDecrementAction(data){ return {type:DECREMENT,data:data} } //异步action,action是个函数,异步action中一般都会调用同步action去真正操作数据,类似vuex4中的act与mut //异步action也可以直接写在组件里,异步调用同步action,即使用对象方式 export function createIncrementAsyncAction(data){ return (dispatch)=>{ setTimeout(()=>{ // 通知redux改变store dispatch(createIncrementAction(data)) },1000) } }
count_reducer.js
//专门为count组件服务的reducer,本质就是一个函数 import { INCREMENT,DECREMENT } from "./constant" /** * * @param {*} preState 之前的对象 * @param {*} aciton 动作对象 */ const initState = 0 //初始化值 // store.js一加载就自己调用了counntReducer export default function countReducer(preState = initState , action){ const {type,data} = action switch(type){ case INCREMENT: return preState + data case DECREMENT: return preState - data default: //没有任何动作,以及不给data(undefined),就代表初始化 return preState } }
index.js(main.js)
import React from 'react' import ReactDOM from 'react-dom/client' import {BrowserRouter} from 'react-router-dom' import store from './redux/store.js' import App from './App.jsx' const root = ReactDOM.createRoot(document.getElementById('root')) root.render( <BrowserRouter> <App /> </BrowserRouter> ) store.subscribe(()=>{ // 检测redux中状态的变化,只要变化就重新调用一次App组件,子组件就会更新视图 root.render(<App/>) })
react-redux
基本使用
- 所有的UI组件都应该包裹着一个容器组件,他们是父子关系
- 容器组件是真正和redux打交道的,容器组件可以任意使用redux的api
- UI组件中不嫩使用任何redux的api
- 容器组件会通过props传递给UI组件:
- redux中所保存的状态
- 用于操作状态的方法
- 容器组件需要react-redux来创建
使用react-redux修改上节实例(没有修改上节redux相关代码,并且下方代码会用到上节的redux代码)
Count容器组件
import { createIncrementAction, createDecrementAction, createIncrementAsyncAction } from '../../redux/count_action' //引入Count的UI组件 import CountUI from '../../components/Count' //引入connect用于连接UI组件和redux import { connect } from 'react-redux' //使用connect()()创建一个Count组件的容器组件,并连接UI组件 // 传递第一个参数映射状态;第二个参数映射操作状态的方法 const Count = connect( mapStateToProps,mapDispatchToProps )( CountUI ) export default Count //state映射为props的 函数 ,返回值对象作为 映射状态(redux),传递给了UI组件CountUI,其实是通过props的模式传递的 function mapStateToProps(state){ //state就属于redux里的状态,react-redux调用这个函数的时候,会把state传过来 return {count:state} } //dispatch映射为props的 函数 ,返回值对象 作为 映射操作状态的方法,传递给了UI组件CountUI,其实是通过props的模式传递的 function mapDispatchToProps(dispatch){ return { //第一种方式function add:(data)=>{ // 通知redux执行加法 dispatch(createIncrementAction(data)) }, jian:data => dispatch(createDecrementAction(data)), addAsync:data=>dispatch(createIncrementAsyncAction(data)) //第二种方式:对象,需要作为对象直接放在上方connext函数里 // 并且,react-redux会自动分发dispatch // add:createIncrementAction, // jian:createDecrementAction, // addAsync:createIncrementAsyncAction } }
渲染容器组件
import React, { Component } from 'react' import store from './redux/store' // import Count from './components/Count' //在react-reux里不能直接用普通组件 import Count from './containers/Count' //必须使用容器组件,因为容器组件一渲染,包裹的ui组件也会渲染了 export default class App extends Component { render() { return ( <div> {/* 这样就把容器组件与redux(store)连接在一起 ,通过props传递*/} <Count store={store}></Count> </div> ) } }
CountUI组件
import React, { Component } from 'react' export default class index extends Component { //加法 increment = () => { const { value } = this.selectNumber //获取下拉框选中的值 this.props.add(parseInt(value)) } //减法 decrement = () => { const { value } = this.selectNumber this.props.jian(parseInt(value)) } //store存的数为奇数再加 incrementIfOdd = () => { const { value } = this.selectNumber if(this.props.count % 2 !== 0){ this.props.add(parseInt(value)) } } //异步加 incrementAsync = () => { const { value } = this.selectNumber this.props.addAsync(parseInt(value)) } render() { console.log('react-redux-props',this.props) return ( <div> <h1>当前求和:{this.props.count}</h1> <select name="" id="" ref={(c) => (this.selectNumber = c)}> <option value="1">1</option> <option value="2">2</option> <option value="3">3</option> <option value="4">4</option> </select> <button onClick={this.increment}>+</button> <button onClick={this.decrement}>-</button> <button onClick={this.incrementIfOdd}>当前求和为奇数加</button> <button onClick={this.incrementAsync}>异步加</button> </div> ) } }
由于使用react-redux,所以不需要再在index.js(main.js)检测redux中的变化了,容器组件会自己检测
优化1: 借助Provider
未优化:
App组件
import React, { Component } from 'react' import store from './redux/store' // import Count from './components/Count' //在react-reux里不能直接用普通组件 import Count from './containers/Count' //必须使用容器组件,因为容器组件一渲染,包裹的ui组件也会渲染了 export default class App extends Component { render() { return ( <div> {/* 这样就把容器组件与redux(store)连接在一起 ,通过props传递*/} <Count store={store}></Count> <Count2 store={store}></Count2> <Count3 store={store}></Count3> <Count4 store={store}></Count4> <Count5 store={store}></Count5> <Count6 store={store}></Count6> <Count7 store={store}></Count7> <Count8 store={store}></Count8> </div> ) } }
使用provier优化后:
App组件
import React, { Component } from 'react' // import Count from './components/Count' //在react-reux里不能直接用普通组件 import Count from './containers/Count' //必须使用容器组件,因为容器组件一渲染,包裹的ui组件也会渲染了 export default class App extends Component { render() { return ( <div> <Count></Count> <Count2></Count2> <Count3></Count3> <Count4></Count4> <Count5></Count5> </div> ) } }
index.js(main.js)
import React from 'react' import ReactDOM from 'react-dom/client' import { BrowserRouter } from 'react-router-dom' import store from './redux/store.js' import { Provider } from 'react-redux' import App from './App.jsx' const root = ReactDOM.createRoot(document.getElementById('root')) root.render( <BrowserRouter> {/* 整个App应用(所有后代组件)里面,但凡需要store的 容器组件 ,Provider都能把store给传递这些组件 */} <Provider store={store}> <App /> </Provider> </BrowserRouter> )
优化2:整合UI组件和容器组件
- 当一个组件需要redux的时候,那么直接把它创建成容器组件containers,并且在该文件里直接创建UI组件,然后原地使用.
- 当一个组件不需要redux的时候,那么就把它创建成普通组件components
- 当一个组件是路由组件的时候,那么就把它创建成路由组件views
例如UI组件和容器组件的整合:
import { createIncrementAction, createDecrementAction, createIncrementAsyncAction } from '../../redux/count_action' //引入connect用于连接UI组件和redux import { connect } from 'react-redux' import React, { Component } from 'react' //引入Count的UI组件,(不推荐) // import CountUI from '../../components/Count' //在容器组件里直接定义UI组件 class CountUI extends Component { //加法 increment = () => { const { value } = this.selectNumber //获取下拉框选中的值 this.props.add(parseInt(value)) } //减法 decrement = () => { const { value } = this.selectNumber this.props.jian(parseInt(value)) } //store存的数为奇数再加 incrementIfOdd = () => { const { value } = this.selectNumber if(this.props.count % 2 !== 0){ this.props.add(parseInt(value)) } } //异步加 incrementAsync = () => { const { value } = this.selectNumber this.props.addAsync(parseInt(value)) } render() { console.log('react-redux-props',this.props) return ( <div> <h1>当前求和:{this.props.count}</h1> <select name="" id="" ref={(c) => (this.selectNumber = c)}> <option value="1">1</option> <option value="2">2</option> <option value="3">3</option> <option value="4">4</option> </select> <button onClick={this.increment}>+</button> <button onClick={this.decrement}>-</button> <button onClick={this.incrementIfOdd}>当前求和为奇数加</button> <button onClick={this.incrementAsync}>异步加</button> </div> ) } } //使用connect()()创建一个Count组件的容器组件,并连接UI组件 // 传递第一个参数映射状态;第二个参数映射操作状态的方法 const Count = connect( mapStateToProps,mapDispatchToProps )( CountUI ) export default Count //state映射为props的 函数 ,返回值对象作为 映射状态(redux),传递给了UI组件CountUI,其实是通过props的模式传递的 function mapStateToProps(state){ //state就属于redux里的状态,react-redux调用这个函数的时候,会把state传过来 return {count:state} } //dispatch映射为props的 函数 ,返回值对象 作为 映射操作状态的方法,传递给了UI组件CountUI,其实是通过props的模式传递的 function mapDispatchToProps(dispatch){ return { //第一种方式function add:(data)=>{ // 通知redux执行加法 dispatch(createIncrementAction(data)) }, jian:data => dispatch(createDecrementAction(data)), addAsync:data=>dispatch(createIncrementAsyncAction(data)) } }
组件与redux打通的步骤
- A文件里定义好UI组件,不暴露
- A文件里通过引入connect生成一个容器组件,并暴露,写法如下:
export default connect( state=>({key:value}),//映射状态 {key:xxxAction} //映射操作状态的方法 )(UI)
- A文件里,在UI组件中通过this.props.xxxxx读取和操作状态
数据共享(多个组件)
共享组件的reducer要使用combineReducers进行合并,合并后的总状态是一个对象state(总).
hooks(未完善)
useSelector()
从redux
的store
对象中获取数据(state
)
useDispatch()
store
中对dispatch
函数的引用