react-redux、Provider、整合容器组件与UI组件、多组件数据共享
-
react-redux:
facebook团队发现程序员都喜欢用redux在react项目中管理自己的状态,于是就开发了react-redux,目的是让程序员更方便的在react中使用redux。
ui组件一般放在components文件夹下,容器组件放在container文件夹下
ui组件的容器组件不用自己去写,用react-redux去写。
安装react-redux
npm install react-redux
react-redux的注意事项:
1、每个ui组件都有一个容器组件,是福字关系
2、容器组件用来和redux打交道
3.ui组件不能有任何redux的api
4、容器组件会传给ui组件:a、redux保存的状态,b、用于操作状态的方法
5、容器传给ui组件的状态和操作状态的方法均通过props
用react-redux实现一下Count组件
一、在containers文件下创建对应的Count容器组件
containers/Count/index.jsx
// 引入Count的ui组件 import CountUI from '../../components/Count' import { createIncrementAction, createDecrementAction, createIncrementAsyncAction } from '../../redux/count_action' // 引入connect,用于连接UI组件与redux import { connect } from 'react-redux' /** * 1.mapStateToProps函数返回的是一个对象: * 2.返回的对象中的key就作为传递给UI组件props的key,value就作为传递给UI组件props的value * 3.mapStateToProps用于传递状态 */ function mapStateToProps(state) { // a函数的参数就是store中的状态 return {count: state} } // 函数返回的对象中的key就作为传递给UI组件props的key,value就作为传递给UI组件props的value---操作状态的方法 /** * 1.mapDispatchToProps函数返回的是一个对象: * 2.返回的对象中的key就作为传递给UI组件props的key,value就作为传递给UI组件props的value * 3.mapDispatchToProps用于传递操作状态的方法 */ function mapDispatchToProps(dispatch) { return { jia: number => dispatch(createIncrementAction(number)), jian: number => dispatch(createDecrementAction(number)), jiaAsync: number => dispatch(createIncrementAsyncAction(number, 500)), } } // 使用connect()()创建并暴露出去一个容器组件 export default connect(mapStateToProps, mapDispatchToProps)(CountUI);
二、Count的ui组件
components/Count/index.jsx
import React, { Component } from 'react' export default class Count extends Component { // 加法 increment = () => { const { value } = this.selectNumber; this.props.jia(value*1); } // 减法 decrement = () => { const { value } = this.selectNumber; this.props.jian(value*1); } // 如果是奇数再加 incrementIfOdd = () => { const { value } = this.selectNumber; if(this.props.count % 2 !== 0) { this.props.jia(value*1); } } // 异步加 incrementAsync = () => { const { value } = this.selectNumber; this.props.jiaAsync(value*1) } render() { console.log(this.props); return ( <div> <h1>当前求和为: {this.props.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> ) } }
三、容器组件的store由其父级通过props传入
这里的Count容器组件是app.jsx
import React, { Component } from 'react' import Count from './containers/Count' import store from './redux/store' export default class app extends Component { render() { return ( <div> <Count store={store} /> </div> ) } }
容器组件内的优化:
1、代码层面优化:把function写成箭头函数,直接传递到第一个参数(mapStateToProps)
2、react-redux的api层面优化:mapDispatchToProps可以写成对象形式,只需要把actiom_creator传递过去,react-redux会自动调用dispatch分发
containers/Count/index.jsx:
// 引入Count的ui组件 import CountUI from '../../components/Count' import { createIncrementAction, createDecrementAction, createIncrementAsyncAction } from '../../redux/count_action' // 引入connect,用于连接UI组件与redux import { connect } from 'react-redux' // 使用connect()()创建并暴露出去一个容器组件 export default connect( state => ({count: state}), // mapDispatchToProps的一般写法 // dispatch => ({ // jia: number => dispatch(createIncrementAction(number)), // jian: number => dispatch(createDecrementAction(number)), // jiaAsync: number => dispatch(createIncrementAsyncAction(number, 500)), // }) // mapDispatchToProps的简写 // 简写形式可以写成对象形式 // 把对应的action_creator传递过期就可以了 { jia: createIncrementAction, jian: createDecrementAction, jiaAsync: createIncrementAsyncAction, } )(CountUI);
Provider:
之前,在index.jsx内,我们为了监听store的变化触发组件render,写了这样一段代码:
// 监听store中值的变化,更新App组件,就不用在每个组件中监听store掉用render了 store.subscribe(() => { root.render(<App />); })
在容器组件内,为了把store传给容器组件,我们通过props的形式把store传了过去,如果项目中有很多的容器组件,每个都有传store,有些不方便,
react-redux的Provider可以优雅的把这两个问题解决,Provider会自动分析项目中的容器组件,把需要store的容器组件传递给每一个容器组件。
在入口文件index.jsx内这样写:
import React from 'react' import ReactDOM from 'react-dom/client'; import App from './App.jsx' import store from './redux/store' import { Provider } from 'react-redux' const root = ReactDOM.createRoot(document.getElementById('root')); root.render( <Provider store={store}> <App /> </Provider> ); // // 监听store中值的变化,更新App组件,就不用在每个组件中监听store掉用render了 // store.subscribe(() => { // root.render(<App />); // })
在使用Count容器组件的地方就可以省去传store的代码:
import React, { Component } from 'react' import Count from './containers/Count' // import store from './redux/store' export default class app extends Component { render() { return ( <div> {/* <Count store={store} /> */} <Count /> </div> ) } }
整合容器组件与ui组件:
上面的例子,我们写了一个Count组件,分别在components中写了一个Count的UI组件,在Container中写了对应的容器组件。
如果项目中有20个组件需要和redux打交道,因为每个UI组件都需要一个容器组件,那么我们就需要再写对应的20个容器组件,总共40个组件,这样就很不方便了,
我们完全可以在一个文件内写两个组件,一个是ui组件,一个是容器组件,最后把容器组件暴露出来:
原来的Count的UI组件:
import React, { Component } from 'react' export default class Count extends Component { // 加法 increment = () => { const { value } = this.selectNumber; this.props.jia(value*1); } // 减法 decrement = () => { const { value } = this.selectNumber; this.props.jian(value*1); } // 如果是奇数再加 incrementIfOdd = () => { const { value } = this.selectNumber; if(this.props.count % 2 !== 0) { this.props.jia(value*1); } } // 异步加 incrementAsync = () => { const { value } = this.selectNumber; this.props.jiaAsync(value*1) } render() { console.log(this.props); return ( <div> <h1>当前求和为: {this.props.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> ) } }
原来的Count的容器组件:
// 引入Count的ui组件 import CountUI from '../../components/Count' import { createIncrementAction, createDecrementAction, createIncrementAsyncAction } from '../../redux/count_action' // 引入connect,用于连接UI组件与redux import { connect } from 'react-redux' // 使用connect()()创建并暴露出去一个容器组件 export default connect( state => ({count: state}), // mapDispatchToProps的一般写法 // dispatch => ({ // jia: number => dispatch(createIncrementAction(number)), // jian: number => dispatch(createDecrementAction(number)), // jiaAsync: number => dispatch(createIncrementAsyncAction(number, 500)), // }) // mapDispatchToProps的简写 // 简写形式可以写成对象形式 // 把对应的action_creator传递过期就可以了 { jia: createIncrementAction, jian: createDecrementAction, jiaAsync: createIncrementAsyncAction, } )(CountUI);
我们把上面的容器组件和UI组件整合到容器组件内,最后暴露出容器组件:
Containers/Count/index.jsx:
import React, { Component } from 'react' import { createIncrementAction, createDecrementAction, createIncrementAsyncAction } from '../../redux/count_action' // 引入connect,用于连接UI组件与redux import { connect } from 'react-redux' // UI组件 class Count extends Component { // 加法 increment = () => { const { value } = this.selectNumber; this.props.jia(value*1); } // 减法 decrement = () => { const { value } = this.selectNumber; this.props.jian(value*1); } // 如果是奇数再加 incrementIfOdd = () => { const { value } = this.selectNumber; if(this.props.count % 2 !== 0) { this.props.jia(value*1); } } // 异步加 incrementAsync = () => { const { value } = this.selectNumber; this.props.jiaAsync(value*1) } render() { console.log(this.props); return ( <div> <h1>当前求和为: {this.props.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> ) } } // 容器组件 export default connect( // mapStateToProps state => ({count: state}), // mapDispatchToProps的一般写法 // dispatch => ({ // jia: number => dispatch(createIncrementAction(number)), // jian: number => dispatch(createDecrementAction(number)), // jiaAsync: number => dispatch(createIncrementAsyncAction(number, 500)), // }) // mapDispatchToProps的简写 { jia: createIncrementAction, jian: createDecrementAction, jiaAsync: createIncrementAsyncAction, } )(Count);
从0开始写一个极简版的Count组件:
import React, { Component } from 'react' import { connect } from 'react-redux' import { createIncrementAction } from '../../redux/count_action' class Count extends Component { add = () => { this.props.jiafa(1) } render() { return ( <div> <h2>当前求和为: {this.props.he}</h2> <button onClick={this.add}>点我加1</button> </div> ) } } export default connect( // 映射状态 state => ({ he: state }), // 映射操作状态的方法 { jiafa: createIncrementAction } )(Count)
多组件数据共享:
前面的例子我们把count_actios.js和count_reducer.js直接放在了redux目录下,这样下去,多个组件就产生很多的xxx_action.js和xxx_reducer.js,索性,在redux目录下创建两个文件夹:
actions和reducers分别用来放置各个组件的action和reducer文件。
我们再创建一个Person组件,让count钻进可以用person的数据,让person组件可以用count的数据,实现数据共享。
把redux文件夹:
在这里需要注意,之前redux中只维护了count组件的一个值,但是现在又要维护person组件的值,这里就需要借助react-redux中的一个函数 combineReducers,把所有需要维护的reducer组合到一起,传给store。
store.js:
/** * 该文件专门用于暴露一个store对象 */ // 引入createStore,专门创建redux中最为核心的store对象 import { createStore, applyMiddleware, combineReducers } from 'redux' // 引入为Count组件服务的reducer import countReducer from './reducers/count' // 引入为Count组件服务的reducer import personReducer from './reducers/person' // 引入redux-thunk,用于支持异步action import thunk from 'redux-thunk' // 汇总所有的reducer变为一个总的reducer const allReducer = combineReducers({ he: countReducer, rens: personReducer }) // 暴露store export default createStore(allReducer, applyMiddleware(thunk));
person组件要增加人,那么reducer中就要多一个增加人的动作类型
redux/constants.js:
/** * 改文件是专门定义action中type类型的常量值 */ export const INCREMENT = 'increment'; export const DECREMENT = 'decrement'; export const ADD_PERSON = 'add_person';
redux/reducers/count.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; } }
redux/reducers/person.js
import { ADD_PERSON } from '../constants' // 初始化人的列表 const initState = [{ id: '001', name: 'tom', age: 18 }]; export default function personReducer(preState = initState, action) { const { type, data } = action; switch(type) { case ADD_PERSON: // 添加一个人 return [data, ...preState] default: return preState; } }
redux/actions/count.js
/** * 改文件专门为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) } }
redux/actions/person.js
import { ADD_PERSON } from '../constants' // 创建增加一个人的action对象 export const createAddPersonAction = personObj => ({ type: ADD_PERSON, data: personObj });
以上准备好了redux,下面写Person组件
containers/Person/index.jsx
import React, { Component } from 'react' import { nanoid } from 'nanoid' import { connect } from 'react-redux' import { createAddPersonAction } from '../../redux/actions/person' class Person extends Component { addPerson = () => { const name = this.nameNode.value; const age = this.ageNode.value; const personObj = { id: nanoid(), name, age }; this.props.jiayiren(personObj); this.nameNode.value = ''; this.ageNode.value = ''; } render() { return ( <div> <h2>我是Person组件,上方组件求和为: {this.props.he}</h2> <input ref={c => this.nameNode = c } type="text" placeholder="输入名字" /> <input ref={c => this.ageNode = c } type="text" placeholder="输入年龄" /> <button onClick={this.addPerson}>添加</button> <ul> { this.props.yiduiyren.map(p => { return <li key={p.id}>名字{p.name}--年龄{p.age}</li> }) } </ul> </div> ) } } export default connect( // mapStateToProps 映射状态 state => ({ yiduiyren: state.rens, he: state.he }), // mapDisPatchToProps 映射操作状态的方法 { jiayiren: createAddPersonAction } )(Person)
containers/Count/index.jsx
import React, { Component } from 'react' import { connect } from 'react-redux' import { createIncrementAction, createDecrementAction, createIncrementAsyncAction } from '../../redux/actions/count' class Count extends Component { // 加法 increment = () => { const { value } = this.selectNumber; this.props.jia(value*1); } // 减法 decrement = () => { const { value } = this.selectNumber; this.props.jian(value*1); } // 如果是奇数再加 incrementIfOdd = () => { const { value } = this.selectNumber; if(this.props.count % 2 !== 0) { this.props.jia(value*1); } } // 异步加 incrementAsync = () => { const { value } = this.selectNumber; this.props.jiaAsync(value*1) } render() { console.log(this.props); return ( <div> <h1>当前求和为: {this.props.count}, 下方组件总人数为:{this.props.renshu}</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> ) } } export default connect( // 映射状态 state => ({ count: state.he, renshu: state.rens.length }), // 映射操作状态的方法 { jia: createIncrementAction, jian: createDecrementAction, jiaAsync: createIncrementAsyncAction, } )(Count)
app.jsx
import React, { Component } from 'react' import Count from './containers/Count' import Person from './containers/Person' export default class app extends Component { render() { return ( <div> <Count /> <hr /> <Person /> </div> ) } }
入口文件index.js
import React from 'react' import ReactDOM from 'react-dom/client'; import App from './App.jsx' import store from './redux/store' import { Provider } from 'react-redux' const root = ReactDOM.createRoot(document.getElementById('root')); root.render( <Provider store={store}> <App /> </Provider> );
react-redux也有开发者工具,不过需要配合一个插件使用
yarn add redux-devtools-extension
在store中引入
/** * 该文件专门用于暴露一个store对象 */ // 引入createStore,专门创建redux中最为核心的store对象 import { createStore, applyMiddleware, combineReducers } from 'redux' // 引入为Count组件服务的reducer import countReducer from './reducers/count' // 引入为Count组件服务的reducer import personReducer from './reducers/person' // 引入redux-thunk,用于支持异步action import thunk from 'redux-thunk' // 引入redux-devtools-extension import { composeWithDevTools } from 'redux-devtools-extension' // 汇总所有的reducer变为一个总的reducer const allReducer = combineReducers({ he: countReducer, rens: personReducer }) // 暴露store // composeWithDevTools,也是在第二个参数 // export default createStore(allReducer, applyMiddleware(thunk)); export default createStore(allReducer, composeWithDevTools(applyMiddleware(thunk)));
-