React中的状态管理---Redux

Posted 前端纸飞机

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了React中的状态管理---Redux相关的知识,希望对你有一定的参考价值。

我们以todolist中增加代办事项这个功能为例:

为了开发规范一点我们要采取reducer的分片写法,即一个类型数据一个模块

不使用react-redux书写规范

生产安装redux

yarn add redux

在src目录下建立store目录

建立index.js和reducer.js

index.js

import { createStore } from 'redux'

import reducer from './reducer'

const store = new createStore ( reducer )

export default store

reducer.js

import { combineReducers } from 'redux'

const reducer = combineReducers({
  
})

export default reducer

建立todos文件夹,依次建立

state.js

const state = {
    todos : [
        {
            id : 1,
            text : '睡觉'
        },
        {
            id : 2,
            text : '吃饭'
        },
        {
            id : 3,
            text : '打豆豆'
        }
    ]
}

export default state

type.js

export const ADD_TODOS = 'ADD_TODOS'

actionCreators.js

import * as type from './type'
import store from '../index'

const actionCreators = {
    addTodos( value ){
        let action = {
            type : type.ADD_TODOS,
            payload : value
        }
        store.dispatch( action )//相当于vue里的commit,把action发送给reducer
    }
}

export default actionCreators

reducer.js

import * as type from './type'
import state from './state'

const reducer = ( previousState = state,action) => {//将state赋值给previousState作为初始值
   let newState = {//结构出来给newState,以后对newState进行操作
       ...previousState
   }
   switch (action.type) {
       case type.ADD_TODOS:
            newState.todos.push({
            id : newState.todos.length + 1,
            text : action.payload
            })
            break;
        default:
            break;  
   }
   console.log(newState)
   return newState
}

export default reducer

这些完成后我们需要把todos文件夹下的reducer拿到src下的reducer使用,即src目录下的reducer.js文件改为这样

import { combineReducers } from 'redux'
import todos from './todos/reducer'

const reducer = combineReducers({
  todos
})

export default reducer

这样一个Redux就完成了,接着就是创建组件然后调用store来

src目录下建立pages目录,建立todos目录,下建立todo的组件

在组件中调用store里的方法,两个文件,TodoInput里是增加任务的,所以我们应当在TodoInput里调用activeCreators里的方法来执行任务

TodoInput.js

import React,{ Component,Fragment } from 'react'

import actionCreators from '../../store/todos/actionCreators'//引入actionCreators

class TodoInput extends Component {

    addTodos = (e) => {
        let keyCode = e.keyCode
        let value = e.target.value
        if ( keyCode === 13 ){
            actionCreators.addTodos(value)//调用actionCreators里的方法
        }
    }
    render () {
        return (
            <Fragment>
                <input onKeyUp={ this.addTodos } type="text"></input>
            </Fragment>
        )
    }
}

export default TodoInput

TodoContent.js

TodoContent里是数据的展示,这里有几个关键点,Redux里视图更新需要store.subscribe来完成,

知识点:

  1. 列表渲染的方法
  2. 怎么让actionCreators里的方法执行后视图更新
import React,{ Component,Fragment } from 'react'
import store from '../../store/index'

const Item = (props) => {
    return (
        <li>{ props.item.text }</li>
    )
}
class TodoContent extends Component {

    constructor () {
        super()
        this.state={
            todos : store.getState().todos.todos//reducer里的名称到todos里的state里的数据名称
        }
    }

    renderItem = () => {//最好的列表渲染方法
        return this.state.todos.map( (item,index)=>{
           return <Item item={ item } key={ item.id }></Item>
        })
    }

    componentDidMount(){//视图更新的方法
        store.subscribe(()=>{
            this.setState({
                todos : store.getState().todos.todos
            })
        })
    }
    render () {
        return (
            <Fragment>
                <ul>
                    { this.renderItem() }
                </ul>
            </Fragment>
        )
    }
}

export default TodoContent

index.js

import TodoInput from './TodoInput'
import TodoContent from './TodoContent'

export { TodoInput,TodoContent }

使用react-redux规范

使用react-redux涉及到的知识点

核心概念

容器组件(智能组件)、UI组件(木偶组件)

react-redux觉得如果一个组件想要使用到store中的数据或者是actionCreator的方法,我们都应该将其变化为容器组件包裹UI组件的样子

其中,容器组件负责连接store,将状态、方法传递给UI组件,UI组件从属性上取得这些api后进行使用

而且,不需要担心是容器组件可以根据UI组件来生成

核心API

Provider 、 connect(mapStateToProps,mapDistpatchToProps)

Provider负责将store相关的api,传入到内部所有的容器组件中

connect负责根据UI组件来生成容器组件

使用方法和细节

我们根据前面没使用react-redux的案例来改造

生产环境安装react-redux

yarn add react-redux

需要在组件的最外层套上Provider组件,并为其传入store

再在主入口文件用 Provider 组件包裹App组件,注意,Provider组件来自react-redux,还需要引入store赋值给Provider

import store from './store'

import { Provider } from 'react-redux'

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

利用connect将需要使用store相关api的组件变成容器组件嵌套UI组件的模式

connect方法的返回值是一个函数,这个函数接收到UI组件之后会返回一个容器组件,容器内部已经嵌套了UI组件

Provider组件会利用context上下文将自己属性中store传递给自己的子级组件,而容器组件会取得context上面的store相关的api

我们可以在connect函数中传入mapStateToProps/mapDispatchToProps参数来掌控容器组件给UI组件传递属性的过程

*mapStateToProps的作用*:

   将store中的state传递到UI组件的属性上
   值为一个函数,接收到的就是store中的state
   返回值是什么,UI组件的属性上就有什么
   并且,因为容器组件中已经做好了store.subscribe的处理,所以一旦store中的状态变化,容器组件就马上能得知,就会重新给UI组件传入新的数据


    *问题*: 坑  ? 我们的store中的数据在更新, 但是里面的容器组件中的 props 不更新

    分析: 先从组件上着手,查看我们书写的代码, 发现代码没有问题的, 这个时候我们就要去查看数据来源, 而数据来源来自于  reducer , 然后输出一下数据, 发现数据渲染了三次 , 有两次是一个组件在创建的时候, 数据从无到有, 还有一次是容器组件产生的 , 后面我们发送我们的new_state一直在重复初始化, 

    解决方案: 
        let newState = {...previousState}

mapDispatchToProps的作用:

   可以将能使用到dispatch的一些方法传递到UI组件上
   值为一个函数,接收到的就是store中的dispatch
   返回上面,UI组件的属性上就有什么
   这个时候actionCreator就变得很纯粹,只需要创建action,dispatch action的动作可以放到mapDispatchToProps的方法中,也就说,在mapDispatchToProps给UI组件传递的函数中将actionCreator创造好的action给dispatch走

但是这样的写法优点不舒服,因为actionCreator里有一个方法,还需要在mapDispatchToProps里再写一个方法

所以可以利用redux中的bindActionCreators将actionCreator中的方法直接放入到UI组件中并且将其返回的action给直接dispatch走


因为在组件中既要用到store中的状态,也要用到actionCreator的方法,导致这个组件引入了很多东西,其实我们可以将这些逻辑封装取出
import React,{ Component,Fragment } from 'react'
import actionCreators from '../../store/todos/actionCreators'
import { connect } from 'react-redux'
import { bindActionCreators } from 'redux'
 
const Item = (props) => {
    return (
        <li>{ props.item.text }</li>
    )
}
class TodoContent extends Component {

    renderItem = () => {
        return this.props.todos.todos.map( (item,index)=>{
           return <Item item={ item } key={ item.id }></Item>
        })
    }
    addTodos = (e) => {
        let keyCode = e.keyCode
        let value = e.target.value
        if ( keyCode === 13 ){
            this.props.addTodos(value)
        }
    }

    render () {
        console.log('render')

        return (
            <Fragment>
                 <input onKeyUp={ this.addTodos } type="text"></input>
                <ul>
                    { this.renderItem() }
                </ul>
            </Fragment>
        )
    }
}

// const mapStateToProps = ( state ) => {//第一种connect方法
//     // return state.todos
//     return {
//         todos : state.todos
//     }
// }

// const mapDispatchToProps = ( dispatch ) => {
//     console.log(dispatch)
//     return bindActionCreators ( actionCreators,dispatch )
// }

// export default connect(mapStateToProps,mapDispatchToProps)(TodoContent)

export default connect(//第二种connect方法
    state => state,
    dispatch => bindActionCreators(actionCreators,dispatch)
)(TodoContent)

注意:倘若方法和数据使用在不同的组件里,例如上面案例的输入框和列表展示不在同一个组件,那么列表展示组件可以只引入

export default connect(
    state => state
)(TodoContent)

任务输入组件需要都使用

export default connect(
    state => state,
    dispatch => bindActionCreators(actionCreators,dispatch)
)(TodoInput)

再修改一下stor下的actionCreators.js的文件

import * as type from './type'

const actionCreators = {
    addTodos( value ){
        let action = {
            type : type.ADD_TODOS,
            payload : value
        }
        return action
    }
}

export default actionCreators

改动的就是由 store.dispatch(action)变成return action,因为我们dispatch功能现在交给组件去做了

react-redux的异步工具redux-thunk

安装redux-thunk

yarn add redux-thunk

使用详细

场景fetch请求一段数据(数据请求是异步的),将todo数据作为数据,根据上面的react-redux后来继续

redux-thunk一个重要的概念,中间件(applyMiddleware)

我们主要修改store目录下的index.js

import { createStore,applyMiddleware } from 'redux'
import thunk from 'redux-thunk'

import reducer from './reducer'

const store = new createStore( reducer,applyMiddleware(thunk) )

export default store 

新的store下的index.js为这样

数据请求放在actionCreators.js里

这里的数据请求中的dispatch( action )的写法相当于return action,同样在使用中是会

import * as type from './type'
const actionCreators = {
    getData(){
        return dispatch =>{
            fetch('/data.json')
            .then(res=>res.json())
            .then(data=>{
                console.log('datad',data)
               let action = {
                   type : type.GET_DATA,
                   payload : data
               }
               dispatch( action )
            })
            .catch(error=>console.log(error)) 
        }   
    },
    addTodos(value){
        let action = {
            type : type.ADD_TODOS,
            payload : value
        }

        return action
    }
}

export default actionCreators

在reducer.js里修改下

import * as type from './type'
import state from './state'

const reducer = (previousState = state,action)=>{
    let newState = {
        ...previousState
    }
    switch(action.type){
        case type.GET_DATA:
            newState.todos=action.payload
            break;
        case type.ADD_TODOS:
            newState.todos.push({
                id : newState.todos.length + 1,
                text : action.payload
            })
            break;
        default:
            break;
    }
    console.log(newState)
    return newState
}

export default reducer

调用

TodoInput.js

import React,{ Component,Fragment } from 'react'
// import { connect } from 'react-redux'
// import  { bindActionCreators } from 'redux'
// import actionCreators from '../../store/todos/actionCreators'

import { connect } from 'react-redux'
import { bindActionCreators } from 'redux'
import actionCreators from '../../store/todos/actionCreators'

class TodoInput extends Component {
    addTodos = (e)=>{
        let keyCode = e.keyCode
        let value = e.target.value
        if (keyCode === 13){
            this.props.addTodos(value)
        }
    }

    getData=()=>{
        this.props.getData()
    }
    render() {
        return (
            <Fragment>
                <button onClick={ this.getData }>获取数据</button>
                <input onKeyUp={ this.addTodos } type="text" placeholder="请输入大粑粑"></input>
            </Fragment>
        )
    }
}

export default connect(
    state => state,//必须添加
    dispatch => bindActionCreators( actionCreators,dispatch )
)(TodoInput)

有个特殊的地方,如果一个组件只需要state,那么可以不要dispatch那一块,如果要dispatch,那么数据一定需要

TodoContent.js

import React,{ Component,Fragment } from 'react'

import { connect } from 'react-redux'

const Item = (props)=>{
    return (<li>{ props.item.text }</li>)
}
class TodoContent extends Component {

    renderItem = () => {
        return this.props.todos.todos.map( (item,index)=>{
            return <Item item= { item } key ={ item.id }></Item>
        })
    }
    
    render () {
        return (
            <Fragment>
                <ul>
                    { this.renderItem() }
                </ul>
            </Fragment>
        )
    }
}

// export default TodoContent

export default connect(
    state => state
)(TodoContent)

总结redux-thunk

它的使用方法及其简单:

  1. 安装redux-thunk
  2. 在创建store的时候使用中间件 import { createStore, applyMiddleware } from 'redux' import thunk from 'redux-thunk' import reducer from './reducer' const store = createStore(reducer, applyMiddleware(thunk) )
  3. 这个时候,actionCreator的方法就可以返回一个能接收到dispatch的一个函数,我们可以在这个函数中进行异步操作之后,将actionCreator创建好的action给发送

以上是关于React中的状态管理---Redux的主要内容,如果未能解决你的问题,请参考以下文章

React中的状态管理---Redux

React中的状态管理---Redux

React中的状态管理

React 中的状态管理

React 中的 Redux 详解:

react--redux状态管理