React react-redux的使用

  1. React react-redux的使用
  2. 安装
  3. 定义store
  4. 在react中使用
  5. 在组件中使用
    1. mapStateToProps
    2. 仅在需要时返回新的对象引用
  6. mapDispatchToProps
    1. mapDispatchToProps简写形式
  7. 用例源码

React react-redux的使用

由于redux是一个独立的库,我们如果需要想在react中更方便的使用redux,推荐使用react-redux这个库,当然不使用这个库也是可以的,那么你需要自己维护全局state,并且订阅state的更新,去修改视图,而react-redux已经帮我们做好了这些东西,并且提供了一些api可以使用。

安装

首先我们需要安装reduxreact-redux

yarn add redux react-redux

定义store

然后我们需要定义store,我们在这里创建一个姓名和年龄的store,并combineReducers,一般存放在单独的文件中,我们放在store.js

import { createStore, combineReducers } from 'redux'

const ageReducer = (state = {age: 18}, action) => {
  switch (action.type) {
    case 'increment':
      return {...state, age: state.age + 1}
    case 'decrement':
      return {...state, age: state.age - 1}
    default:
      return state
  }
}

const nameReducer = (state = {name: 'jerry'}, action) => {
  switch (action.type) {
    case 'changeName':
      return {...state, name: action.payload.name}    
    default:
      return state
  }
}

const reducers = combineReducers({ageReducer, nameReducer});

const store = createStore(reducers)

export default store

在react中使用

如何将storereact app之间关联起来呢?这就是react-redux的作用,react-redux提供一个Provider组件,将store绑定到组件,一般我们直接绑定到根组件app上,那么子组件中也可以使用store

import React from 'react';
import ReactDOM from 'react-dom';

import App from './App';

import { Provider } from 'react-redux'
import store from './store'

ReactDOM.render(
  <Provider store={store}>
    <App />
  </Provider>,
  document.getElementById('root')
);

在组件中使用

store已经可用,但是在组件中要怎么使用,怎么拿到state,又怎么派发action呢?此时需要使用到react-redux提供的connect高阶组件了,这个是可选的,如果你的组件不需要访问store,就可以不使用connect

来看看具体用法,假设我们有一个组件User.js,渲染姓名和年龄,注意我们使用的时候不提供nameage`` props

import React from 'react'

class User extends React.Component {
    constructor(props){
        super(props)
    }

    render() {
        return <div>
            姓名: {this.props.name}
            <br />
            年龄: {this.props.age}
        </div>
    }
}

export default User

此时肯定是渲染不出来的,因为我们还没有使用connect连接store<User />上也没有提供相应的props

现在我们来连接store,就使用到了connect高阶组件,connect接收两个函数作为参数,我们习惯性的称之为mapStateToPropsmapDispatchToProps,顾名思义,就是把statedispatch映射到props上,使我们的组件可以通过props属性访问statedispatch,通过dispatch,就可以派发action了。第二个函数是可选的,如果不传,会直接把dispatch映射到组件的props

mapStateToProps

我们先来看看mapStateToProps怎么用,我们打印一下state,看看是什么东西

import React from 'react'
import { connect } from 'react-redux'

class User extends React.Component {
    constructor(props){
        super(props)
    }

    render() {
        return <div>
            姓名: {this.props.name}
            <br />
            年龄: {this.props.age}
        </div>
    }
}

const mapStateToProps = (state) => {
    console.log(state)
}

const ConnectedUser = connect(mapStateToProps)(User)

export default ConnectedUser

可以看到,就是state对象,注意我们是使用combineReducers过的,是有key的,具体可以查看Redux combineReducers

从上图报错也可以看出来,mapStateToProps需要返回一个纯对象,因此我们可以拿到所有的state后,只挑选出对自己有用的,如果都需要或者图方便,直接返回{...state}即可

// ...
const mapStateToProps = (state) => {
    return {...state}
}
// ...

此时我们就可以在组件的props属性上获取到state了,渲染的时候要根据state的结构进行渲染,因此上面的this.props.namethis.props.age需要修改一下

import React from 'react'
import { connect } from 'react-redux'

class User extends React.Component {
    constructor(props){
        super(props)
    }

    render() {
        console.log(this.props)
        return <div>
            姓名: {this.props.nameReducer.name}
            <br />
            年龄: {this.props.ageReducer.age}
        </div>
    }
}

const mapStateToProps = (state) => {
    console.log(state)
    return {...state}
}

const ConnectedUser = connect(mapStateToProps)(User)

export default ConnectedUser

实际上,如果我们的组件有自己的props,例如有一个性别属性<User gender="male" />mapStateToProp函数还接受第二个参数,可以获取到组件自己的属性,如果只传了state参数,会自动进行合并,不幸的是,如果组件自身的props和state的属性有重名的,组件自身的属性将会丢失,因此对于这种情况,mapStateToProp函数让你接受第二个参数,就是组件自身的props,具体保留哪个,我们可以自行指定。

例如我们是组件以自身的属性优先,就可以直接return { ...state, ...props },此时组件自身的值总是会覆盖state的值
例如这样使用组件<User ageReducer="male" />

import React from 'react'
import { connect } from 'react-redux'

class User extends React.Component {
    constructor(props){
        super(props)
    }

    render() {
        console.log(this.props)
        return <div>
            姓名: {this.props.nameReducer.name}
            <br />
            年龄: {this.props.ageReducer.age}
        </div>
    }
}

const mapStateToProps = (state, props) => {
    console.log(state)
    return {...state, ...props}
}

const ConnectedUser = connect(mapStateToProps)(User)

export default ConnectedUser

看完记得记得把<User ageReducer="male" />还原

仅在需要时返回新的对象引用

我们从store中取出的state还可以结合组件自身props进行一些计算操作,例如排序,复杂计算等,但是如果非常耗时,可能会影响性能.

react-redux 内部实现了shouldComponentUpdate方法以便在组件用到的数据发生变化后能够精确地重新渲染。默认地,react-redux使用===mapStateToProps返回的对象的每一个字段逐一对比,以判断内容是否发生了改变。

react-redux进行浅比较来检查mapStateToProps的结果是否改变了。返回一个新对象或数组引用十分容易操作,但会造成你的组件在数据没变的情况下也重新渲染。

我们建议将所有复杂的查找和计算数据的方法封装到selector中。此外,你今后可以通过使用Reselect编写“memoizedselectors来跳过不必要的工作从而优化性能。还可以将复杂计算放到组件内部去做计算,使用useMemeuseCallback进行优化

mapDispatchToProps

在来看看如何dispatch一个action,上面看到,dispatch函数已经是默认被传递到组件的props属性上了,如果不做操作,可以直接使用this.props.dispatch派发一个action

例如:

import React from 'react'
import { connect } from 'react-redux'

class User extends React.Component {
    constructor(props){
        super(props)
    }

    handleIncrementClick = () => {
        this.props.dispatch({type: 'increment'})
    }

    handleDecrementClick = () => {
        this.props.dispatch({type: 'decrement'})
    }

    render() {
        console.log(this.props)
        return <div>
            姓名: {this.props.nameReducer.name}
            <br />
            年龄: {this.props.ageReducer.age}
            <br />
            <button onClick={this.handleIncrementClick}>+</button>
            <button onClick={this.handleDecrementClick}>-</button>
        </div>
    }
}

const mapStateToProps = (state, props) => {
    console.log(state)
    return {...state, ...props}
}

const ConnectedUser = connect(mapStateToProps)(User)

export default ConnectedUser

这样做没问题,但是直接在组件内部派发action,组件里面可能很多地方都需要dispatch,分散在组件的各个地方,代码维护起来就会十分困难,如果在组件内部还要继续向子组件里面传递dispatch,再在子组件内部派发action,就非常混乱了,并且暴露了父层的dispatch给子组件了,子组件如果内部还有自己的connect,就会非常混乱。

此时借助connect的第二个参数,我们可以在mapDispatchToProps里面集中定义组件的需要的dispatch方法,再传给组件,这样集中管理起来代码维护起来成本就降低很多了。

怎么用呢?mapDispatchToProps接收dispatch作为参数,返回一个派发action的函数对象,一旦定义了这个函数,dispatch就不会默认传给组件了,需要自己定义。

import React from 'react'
import { connect } from 'react-redux'

class User extends React.Component {
    constructor(props){
        super(props)
    }

    handleIncrementClick = () => {
        this.props.ageIncrement()
    }

    handleDecrementClick = () => {
        this.props.ageDecrement()
    }
    handleNameChange = () => {
        this.props.changeName({name: 'tom'})
    }

    render() {
        console.log(this.props)
        return <div>
            姓名: {this.props.nameReducer.name}
            <br />
            年龄: {this.props.ageReducer.age}
            <br />
            <button onClick={this.handleIncrementClick}>+</button>
            <button onClick={this.handleDecrementClick}>-</button>
            <button onClick={this.handleNameChange}>改变名字</button>
        </div>
    }
}

const mapStateToProps = (state, props) => {
    console.log(state)
    return {...state, ...props}
}

const mapDispatchToProps = (dispatch) => {
    return {
        ageIncrement: payload => dispatch({type: 'increment', payload}),
        ageDecrement: payload => dispatch({type: 'decrement', payload}),
        changeName: payload => dispatch({type: 'changeName', payload}),
    }
}

const ConnectedUser = connect(mapStateToProps, mapDispatchToProps)(User)

export default ConnectedUser

这样的话,我们自己定义的派发action函数ageIncrementageDecrementchangeName就会放到组件的props属性上了

mapDispatchToProps简写形式

使用上述方式定义mapDispatchToProps看起来有点累赘,react-redux也帮我们想到了,在connect函数的第二个参数上,可以直接传递一个action creator函数的数组,然后使用bindActionCreators API来帮我们进行处理,bindActionCreators的使用具体请看Redux bindActionCreators

我们对mapDispatchToProps进行改写,这样看起来好多了,功能也没有受到影响。

// ...

// const mapDispatchToProps = (dispatch) => {
//     return {
//         ageIncrement: payload => dispatch({type: 'increment', payload}),
//         ageDecrement: payload => dispatch({type: 'decrement', payload}),
//         changeName: payload => dispatch({type: 'changeName', payload}),
//     }
// }

const ConnectedUser = connect(mapStateToProps, {
    ageIncrement(payload) {
        return {type: 'increment', payload}
    },
    ageDecrement(payload) {
        return {type: 'decrement', payload}
    },
    changeName(payload) {
        return {type: 'changeName', payload}
    }
})(User)

export default ConnectedUser

react-redux的基本使用就这些了,如果想要继续深入学习,可以看一下它的源码,和中文文档

现在都流行hooksreact-redux也实现了hooks,留坑,有时间再更新吧。

用例源码

store.js

import { createStore, combineReducers } from 'redux'

const ageReducer = (state = {age: 18}, action) => {
  switch (action.type) {
    case 'increment':
      return {...state, age: state.age + 1}
    case 'decrement':
      return {...state, age: state.age - 1}
    default:
      return state
  }
}

const nameReducer = (state = {name: 'jerry'}, action) => {
  switch (action.type) {
    case 'changeName':
      return {...state, name: action.payload.name}    
    default:
      return state
  }
}

const reducers = combineReducers({ageReducer, nameReducer});

const store = createStore(reducers)

export default store

index.js

import React from 'react';
import ReactDOM from 'react-dom';

import App from './App';

import { Provider } from 'react-redux'
import store from './store'

ReactDOM.render(
  <Provider store={store}>
    <App />
  </Provider>,
  document.getElementById('root')
);

app.js

import React, { Profiler } from 'react';
import SagaTest from './components/redux/storeTest'

function App () {
  return (
    <div className="App">
      <SagaTest gender="male"></SagaTest>
    </div>
  );
}

export default App;

src/components/redux/storeTest.js

import React from 'react'
import { connect } from 'react-redux'

class User extends React.Component {
    constructor(props){
        super(props)
    }

    handleIncrementClick = () => {
        this.props.ageIncrement()
    }

    handleDecrementClick = () => {
        this.props.ageDecrement()
    }
    handleNameChange = () => {
        this.props.changeName({name: 'tom'})
    }

    render() {
        console.log(this.props)
        return <div>
            姓名: {this.props.nameReducer.name}
            <br />
            年龄: {this.props.ageReducer.age}
            <br />
            <button onClick={this.handleIncrementClick}>+</button>
            <button onClick={this.handleDecrementClick}>-</button>
            <button onClick={this.handleNameChange}>改变名字</button>
        </div>
    }
}

const mapStateToProps = (state, props) => {
    console.log(state)
    return {...state, ...props}
}

// const mapDispatchToProps = (dispatch) => {
//     return {
//         ageIncrement: payload => dispatch({type: 'increment', payload}),
//         ageDecrement: payload => dispatch({type: 'decrement', payload}),
//         changeName: payload => dispatch({type: 'changeName', payload}),
//     }
// }

const ConnectedUser = connect(mapStateToProps, {
    ageIncrement(payload) {
        return {type: 'increment', payload}
    },
    ageDecrement(payload) {
        return {type: 'decrement', payload}
    },
    changeName(payload) {
        return {type: 'changeName', payload}
    }
})(User)

export default ConnectedUser

转载请注明来源,欢迎对文章中的引用来源进行考证,欢迎指出任何有错误或不够清晰的表达。可以在下面评论区评论,也可以邮件至 289211569@qq.com