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>&nbsp;
        <button onClick={this.increment}>+</button>&nbsp;
        <button onClick={this.decrement}>-</button>&nbsp;
        <button onClick={this.incrementIfOdd}>当前求和为奇数再加</button>&nbsp;
        <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>&nbsp;
        <button onClick={this.increment}>+</button>&nbsp;
        <button onClick={this.decrement}>-</button>&nbsp;
        <button onClick={this.incrementIfOdd}>当前求和为奇数再加</button>&nbsp;
        <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>&nbsp;
        <button onClick={this.increment}>+</button>&nbsp;
        <button onClick={this.decrement}>-</button>&nbsp;
        <button onClick={this.incrementIfOdd}>当前求和为奇数再加</button>&nbsp;
        <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>&nbsp;
        <button onClick={this.increment}>+</button>&nbsp;
        <button onClick={this.decrement}>-</button>&nbsp;
        <button onClick={this.incrementIfOdd}>当前求和为奇数再加</button>&nbsp;
        <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)));

 

 

 

 

 

 

 

 

 

 

 

 

-

posted @ 2022-10-19 22:52  古墩古墩  Views(719)  Comments(0Edit  收藏  举报