Redux——结合案例一步步深入学习

文章目录

  • Redux介绍
      • 了解Redux
      • 安装
      • Redux的设计思想
      • redux有四个组成部分
      • Redux流程
      • Redux是纯函数
  • TodoList案例
    • 实现效果
    • 实现思路
      • 1.封装组件
      • 2.redux的实现
      • 3.组件
  • TodoList + Calculator + react-redux 案例
    • 实现效果
    • 实现思路
      • 1.封装组件
      • 2.redux 和 react-redux
        • reducer状态的拆分
        • TodoList
        • Calculator
      • 3.组件
        • TodoList组件
        • Calculator组件
  • redux-thunk 中间件
    • 实现效果
    • 实现思想
  • redux使用总结(以TodoList案例为例)
      • **安装**`npm i redux react-redux redux-thunk`
      • 1.**创建redux管理,reducer的派发,**
      • 2.**在组件中的使用,react-redux的使用**
      • 3.redux-thunk可以进行异步操作

Redux介绍

Rudex官方文档
Rudex英文原版文档

了解Redux

Redux 是 JavaScript 状态容器,提供可预测化的状态管理。

React 只是 DOM 的一个抽象层,并不是 Web 应用的完整解决方案。有两个方面,它没涉及。

  • 代码结构
  • 组件之间的通信

2014年 Facebook 提出了 Flux 架构的概念,引发了很多的实现。2015年,Redux 出现,将 Flux 与函数式编程结合一起,很短时间内就成为了最热门的前端架构。

如果你不知道是否需要 Redux,那就是不需要它

只有遇到 React 实在解决不了的问题,你才需要 Redux

简单说,如果你的UI层非常简单,没有很多互动,Redux 就是不必要的,用了反而增加复杂性。

  • 用户的使用方式非常简单
  • 用户之间没有协作
  • 不需要与服务器大量交互,也没有使用 WebSocket
  • 视图层(View)只从单一来源获取数据

需要使用redux的项目:

  • 用户的使用方式复杂
  • 不同身份的用户有不同的使用方式(比如普通用户和管理员)
  • 多个用户之间可以协作
  • 与服务器大量交互,或者使用了WebSocket
  • View要从多个来源获取数据

从组件层面考虑,什么样子的需要redux:

  • 某个组件的状态,需要共享
  • 某个状态需要在任何地方都可以拿到
  • 一个组件需要改变全局状态
  • 一个组件需要改变另一个组件的状态

安装

  • 在React项目里进行安装
    npm install --save redux

Redux的设计思想

  1. Web 应用是一个状态机,视图与状态是一一对应的。
  2. 所有的状态,保存在一个对象里面(唯一数据源)。

redux有四个组成部分

store:用来存储数据
reducer:真正的来管理数据
actionCreators:创建action,交由reducer处理
view: 用来使用数据,在这里,一般用react组件来充当
Redux——结合案例一步步深入学习_第1张图片
Redux——结合案例一步步深入学习_第2张图片
1.创建store

从redux工具中取出createStore去生成一个store

2.创建一个reducer,然后将其传入到createStore中辅助store的创建

reducer是一个纯函数,接收当前状态和action,返回一个状态,返回什么,store的状态就是什么,需要注意的是,不能直接操作当前状态,而是需要返回一个新的状态

想要给store创建默认状态其实就是给reducer一个参数创建默认值

3.组件通过调用store.getState方法来使用store中的数据

4.组件产生用户操作调用actionCreator的方法创建一个action,利用store.dispatch方法传递给reducer

5.reducer对action上的标示性信息做出判断后对新状态进行处理,然后返回新状态,这个时候store的数据就会发生改变 reducer返回什么状态,store.getState就可以获取什么状态

6.我们可以在组件中,利用store.subscribe(callback)方法去订阅数据的变化,也就是可以传入一个函数,当数据变化的时候,传入的函数会执行,在这个函数中让组件去获取最新的状态

Redux流程

1.store通过reducer创建了初始状态
2.view通过store.getState()获取到了store中保存的state挂载在了自己的状态上
3.用户产生了操作,调用了actions 的方法
4.actions的方法被调用,创建了带有标示性信息的action
5.actions内部通过调用store.dispatch方法将标志性的action发送到了reducer中
6.reducer接收到action并根据标识信息判断之后返回了新的state
7.store的state被reducer更改为新state的时候,store.subscribe方法里的回调函数会执行,此时就可以通知view去重新获取state

Redux是纯函数

reducer是state最终格式的确定。它是一个纯函数,也就是说,只要传入参数相同,返回计算得到的下一个 state 就一定相同。

没有特殊情况、没有副作用,没有 API 请求、没有变量修改,单纯执行计算。
reducer对传入的action进行判断,然后返回一个通过判断后的state,这就是reducer的全部职责

Reducer 函数最重要的特征是,它是一个纯函数。也就是说,只要是同样的输入,必定得到同样的输出。

纯函数是函数式编程的概念,必须遵守以下一些约束永远不要在 reducer 里做这些操作:。

  • 修改传入参数

  • 执行有副作用的操作,如 API 请求和路由跳转

  • 调用非纯函数,如 Date.now() 或 Math.random()

由于 Reducer 是纯函数,就可以保证同样的State,必定得到同样的 View。但也正因为这一点,Reducer 函数里面不能改变 State,必须返回一个全新的对象,请参考下面的写法。

    // State 是一个对象
    function reducer(state, action) {
      return Object.assign({}, state, { thingToChange });
      // 或者
      return { ...state, ...newState };
    }
    
    // State 是一个数组
    function reducer(state, action) {
      return [...state, newItem];
    }

最好把 State 对象设成只读。你没法改变它,要得到新的 State,唯一办法就是生成一个新对象。这样的好处是,任何时候,与某个 View 对应的 State 总是一个不变的对象。

TodoList案例

实现效果

Redux——结合案例一步步深入学习_第3张图片
Redux——结合案例一步步深入学习_第4张图片

实现思路

通过npm i redux安装redux

1.封装组件

src/index.js

import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';
import store from "./store"
window.store = store;  
//组件可以通过store.getState()获取到新的状态了,并且reducer返回什么,这个结果就是什么
//console.log(store)  {dispatch: ƒ, subscribe: ƒ, getState: ƒ, replaceReducer: ƒ, Symbol(observable): ƒ}
// console.log(store.getState()) 得到数据
ReactDOM.render(
    <App /> 
  ,
  document.getElementById('root')
);

src/App.js

import React,{Component} from 'react';
import TodoList from "./components"
class App extends Component{
  render(){
    return (
      <div>
        <TodoList></TodoList>
      </div>
    )
  }
}

export default App;

src/components/index.js

import React, { Component } from 'react'
import TodoInput from "./TodoInput"
import TodoContent from "./TodoContent"
import TodoInfo from "./TodoInfo"

export default class index extends Component {
    render() {
        return (
            <div>
                <TodoInput></TodoInput>
                <TodoContent></TodoContent>
                <TodoInfo></TodoInfo>
            </div>
        )
    }
}

2.redux的实现

store/actionType.js
action上的标示性信息
如果在actionCreators里面将action的type写错名字了,那么就在reducers里面匹配不上,内部就会走default
返回了之前的状态。
而redux的思想:状态与视图是一一对应的。状态不变,那么组件的视图结构就不会发生任何的变化

export const ADD_NEW_TODO="ADD_NEW_TODO"
export const CHANGE_NEW_TODO="CHANGE_NEW_TODO"
export const REMOVE_NEW_TODO="REMOVE_NEW_TODO"

store/actionCreators.js
组件产生用户操作,调用actionCreator的方法创建一个action,利用store.dispatch方法传递给reducer

import store from "./index"
import {ADD_NEW_TODO,REMOVE_NEW_TODO,CHANGE_NEW_TODO} from "./actionType"
export default{   //面试题?   actionCreators or action 关系?   actionCreators创建action    action 一般是一个对象  type 通常写为type
    addNewTodo(title){
        let action={ //一般的action是具有标志性的信息的对象
            type:ADD_NEW_TODO,
            title
        }
        //需要将action给reducer传递过去
        store.dispatch(action)
    },
    changeNewTodo(id){
        let action={
            type:CHANGE_NEW_TODO,
            id
        }
        store.dispatch(action)
    },
    removeNewTodo(id){
        let action={
            type:REMOVE_NEW_TODO,
            id
        }
        store.dispatch(action)
    }
}

store/state.js
初始数据,reducer中参数默认值

export default {
    todos:[
        {id:1,title:"今天周一",isFinished:false},
        {id:2,title:"检查作业",isFinished:true}
    ]
}

store/reducer.js
创建一个reducer,然后将其传入到createStore中辅助store的创建
想要给store创建默认状态其实就是给reducer一个参数创建默认值
reducer对action上的标示性信息做出判断后对新状态进行处理,然后返回新状态,这个时候store的数据就会发生改变 reducer返回什么状态,store.getState就可以获取什么状态
redux内部判别是否派发新状态依据是根据原状态的内存地址与新状态的内存地址的比较

//reducer必须是一个纯函数
//固定的输入必须要有固定的输出,内部函数不能有不纯粹的操作 Math.random()  new Date()
//不能对之前的状态进行任何的更改 内部必须是同步的
//redux思想  状态与视图是一一对应的
import {ADD_NEW_TODO,REMOVE_NEW_TODO,CHANGE_NEW_TODO} from "./actionType"
import state from "./state"
const reducer = (prevState = state,action)=>{  //state={todos:[]}
    let new_state={...prevState}
    switch(action.type){
        case ADD_NEW_TODO:
            new_state.todos.push({id:handler.getId(new_state.todos),title:action.title,isFinished:false})
            break;
        case CHANGE_NEW_TODO:
            new_state.todos=handler.changeNewTodo(new_state.todos,action.id)
            break;
        case REMOVE_NEW_TODO:
            new_state.todos=handler.removeNewTodo(new_state.todos,action.id)
            break;
        default:
            break;
    }
    return new_state   //通过store.getState()其实就是获取到了reducer返回的内容
}
export default reducer


const handler={
    getId(todos){
        todos=todos.slice()
        if(todos.length === 0) return 1;
        return todos.sort((a,b)=>{
            return b.id-a.id
        })[0].id+1
    },
    changeNewTodo(todos,id){
        for(var i=0;i<todos.length;i++){
            if(todos[i].id===id){  //正是想要更改的这一项
                todos[i].isFinished=!todos[i].isFinished
            }
        }
        return todos
    },
    removeNewTodo(todos,id){
        todos=todos.slice()
        return todos.filter(item=> item.id!==id)
    }
}

store/index.js
从redux工具中取出createStore去生成一个store,可以通过createStore()创建一个store,参数需要接受reducer

import {createStore} from "redux"
import reducer from "./reducer"
//可以通过createStore()创建一个store,参数需要接受reducer
const store = createStore(reducer)
export default store

3.组件

组件通过调用store.getState方法来使用store中的数据
我们可以在组件中,利用store.subscribe(callback)方法去订阅数据的变化,也就是可以传入一个函数,当数据变化的时候,传入的函数会执行,在这个函数中让组件去获取最新的状态
src/components/TodoContent
列表显示

import React, { Component } from 'react'
import store from "../store"
import actionCreators from "../store/actionCreators"

//函数式组件(无状态组件)
const LiItem=props=>{
    const handleChange=()=>{
        //需要更改redux的状态,所以应该派发action进行更改
        actionCreators.changeNewTodo(props.item.id)
    }
    const handleRemove=()=>{
        actionCreators.removeNewTodo(props.item.id)
    }
    return(
        <li>
            <input type="checkbox" onChange={handleChange} checked={props.item.isFinished}/> 
            {props.item.title}
            <button onClick={handleRemove}>删除</button>
        </li>
    )
}

export default class TodoContent extends Component {
    constructor(){
        super()
        this.state={
            todos:[]
        }
    }
    setTodos=()=>{
        this.setState({
            todos:store.getState().todos
        })
    }
    componentDidMount(){
        this.setTodos()
        //组件想要获取redux最新状态的话,需要调用store.subscribe订阅方法才可以
        //监控reducer的新状态的返回,如果reducer返回新状态,那么这个callback就会执行
        store.subscribe(()=>{
            this.setTodos()
        })
    }

    renderItem(){
        let {todos}=this.state
        return (
            todos.map(item=>{
                return (
                <LiItem item={item}  key={item.id }/>
                )
            })
        )
    }

    render() {
        return (
            <div>
                <ul>
                {this.renderItem()}
                </ul>
            </div>
        )
    }
}

src/components/TodoInfo
计算显示数量

import React, { Component } from 'react'
import store from "../store"

export default class Todoinfo extends Component {
    constructor(){
        super()
        this.state={
            todos:store.getState().todos
        }
    }
    computed(){
        let {todos}=this.state
        let all={total:0,finish:0,unfinish:0}
        if(todos.length===0) return all;
        all.total = todos.length
        todos.forEach(item=>{
            if(item.isFinished){
                all.finish++
            }else{
                all.unfinish++
            }
        })
        return all
    }

    componentDidMount(){
        //订阅状态的变化
        store.subscribe(()=>{
            this.setState({
                todos:store.getState().todos
            })
        })
    }

    render() {
        let all=this.computed()
        return (
            <div>
                <span>总共{all.total}</span>
        <span>完成了{all.finish}</span>
        <span>未完成{all.unfinish}</span>
            </div>
        )
    }
}

src/components/TodoInput
输入框

import React, { Component, createRef } from 'react'
import actionCreators from "../store/actionCreators"

export default class TodoInput extends Component {
    constructor() {
        super()
        this.input = createRef()
    }
    handleKeyUp = (e) => {
        if (e.keyCode === 13) {
            //通过actionCreators创建action派发给reducer进行处理
            actionCreators.addNewTodo(this.input.current.value)
            this.input.current.value = ""
        }
    }
    render() {
        return (
            <div>
                <input ref={this.input} type="text" onKeyUp={this.handleKeyUp} />
            </div>
        )
    }
}

TodoList + Calculator + react-redux 案例

官方网站

  • 通过npm i react-redux安装react-redux

  • 在之前的案例里我们发现
    我们发现我们使用redux状态的时候,
    我们发现在组件里面都需要引入store
    定义组件的初始化状态
    监听redux状态的变化

  • 如何使用react-redux
    需要在src/index.js上面使用Providestore属性
    需要在components/todolist/TodoInfo.js文件使用redux状态

  • 使得actionCreators变得更加的纯粹,只需要return action就可以了。通过connect()高阶组件
    const mapDispatchToProps = actionCreators
    export default connect(null,mapDispatchToProps)(TodoInput)

    connect(mapState,mapDispatch)(reactUI组件)

    第一个mapState就是可以让组件通过属性props的方式去访问redux的共享状态
    第二个mapDispatch可以是一个函数Object可以让组件通过属性的方式去访问到更改redux状态的方法了,并且派发action到reducer

实现效果

Redux——结合案例一步步深入学习_第5张图片
Redux——结合案例一步步深入学习_第6张图片

实现思路

1.封装组件

src/App.js

import React,{Component} from 'react';
import TodoList from "./components/todolist"
import Calculator from "./components/calculator"
class App extends Component{
  render(){
    return (
      <div>
        <TodoList></TodoList>
        <Calculator></Calculator>
      </div>
    )
  }
}

export default App;

src/index.js

需要在src/index.js上面使用Providestore属性

import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';
import store from "./store"
import {Provider} from "react-redux"

ReactDOM.render(
  //这边通过store属性传下去,那么所有的Provider的后代组件都可以通过connect连接,然后获取redux的状态了
   <Provider store={store}>
     <App/>
   </Provider>
  ,
  document.getElementById('root')
);

2.redux 和 react-redux

reducer状态的拆分

src/store/index

import {createStore} from "redux"
import reducer from "./reducer"  //这个地方的reducer是合并好了的reducer
//可以通过createStore()创建一个store,参数需要接受reducer
const store = createStore(reducer)
export default store

src/store/reducer.js

通过combineReducers来去合并所有的分支reducer
import {combineReducers} from "redux"

import {combineReducers} from "redux"
import todolist from "./todolist/reducer"
import calculator from "./calculator/reducer"
//通过combineReducers来去合并所有的分支reducer
const reducer =combineReducers({
    todolist,
    calculator
})
export default reducer

TodoList

src/store/todolist/state.js

export default {
    todos:[
        {id:1,title:"今天周一",isFinished:false},
        {id:2,title:"检查作业",isFinished:true}
    ]
}

src/store/todolist/reducer.js

//reducer必须是一个纯函数
//固定的输入必须要有固定的输出,内部函数不能有不纯粹的操作 Math.random()  new Date()
//不能对之前的状态进行任何的更改 内部必须是同步的
//redux思想  状态与视图是一一对应的

//新的状态与之前的状态通过引用地址进行比较的,如果操作的新状态的地址与原状态一样,内部就会认为没有返回新的状态,那么视图也不会有新的变化
import state from "./state"
import {ADD_NEW_TODO,REMOVE_NEW_TODO,CHANGE_NEW_TODO} from "./actionType"
const reducer = (prevState = state,action)=>{  //prevState={todos:[]}
    let new_state={...prevState}  //new_state={todos:[]}    浅拷贝 {...}object.assign vs  深拷贝  (递归, 对象  JSON stringify  parse)
    switch(action.type){
        case ADD_NEW_TODO: 
            new_state.todos=new_state.todos.slice()  //将之前的状态拷贝出来新的一份地址不一样
            new_state.todos.push({id:handler.getId(new_state.todos),title:action.title,isFinished:false})
            break;
        case CHANGE_NEW_TODO:
            new_state.todos=handler.changeNewTodo(new_state.todos,action.id)
            break;
        case REMOVE_NEW_TODO:
            new_state.todos=handler.removeNewTodo(new_state.todos,action.id)
            break;
        default:
            break;
    }
    return new_state   //通过store.getState()其实就是获取到了reducer返回的内容
}
export default reducer


const handler={
    getId(todos){
        todos=todos.slice()
        if(todos.length === 0) return 1;
        return todos.sort((a,b)=>{
            return b.id-a.id
        })[0].id+1
    },
    changeNewTodo(todos,id){
        todos=todos.slice()
        for(var i=0;i<todos.length;i++){
            if(todos[i].id===id){  //正是想要更改的这一项
                todos[i].isFinished=!todos[i].isFinished
            }
        }
        return todos
    },
    removeNewTodo(todos,id){
        todos=todos.slice()
        return todos.filter(item=> item.id!==id)
    }
}

src/store/todolist/actionType.js

import {ADD_NEW_TODO,REMOVE_NEW_TODO,CHANGE_NEW_TODO} from "./actionType"
export default{   //面试题?   actionCreators or action 关系?   actionCreators创建action    action 一般是一个对象  type 通常写为type
    addNewTodo(title){
        let action={ //一般的action是具有标志性的信息的对象
            type:ADD_NEW_TODO,
            title
        }
        //需要将action给reducer传递过去
        return action
    },
    changeNewTodo(id){
        let action={
            type:CHANGE_NEW_TODO,
            id
        }
        return action
    },
    removeNewTodo(id){
        let action={
            type:REMOVE_NEW_TODO,
            id
        }
        return action
    }
}


src/store/todolist/actionCreators

import {ADD_NEW_TODO,REMOVE_NEW_TODO,CHANGE_NEW_TODO} from "./actionType"
export default{   //面试题?   actionCreators or action 关系?   actionCreators创建action    action 一般是一个对象  type 通常写为type
    addNewTodo(title){
        let action={ //一般的action是具有标志性的信息的对象
            type:ADD_NEW_TODO,
            title
        }
        //需要将action给reducer传递过去
        return action
    },
    changeNewTodo(id){
        let action={
            type:CHANGE_NEW_TODO,
            id
        }
        return action
    },
    removeNewTodo(id){
        let action={
            type:REMOVE_NEW_TODO,
            id
        }
        return action
    }
}


Calculator

src/store/caculator/state.js

export default{
    prevNumber:0,
    nextNumber:0,
    operator:"+",
    result:0
}

src/store/caculator/reducer.js

import state from "./state"
import {CHANGE_NUMBER,COMPUTE}  from "./actionType"


const reducer=(prevState=state,action)=>{
    let new_state={...prevState}
    switch(action.type){
        case CHANGE_NUMBER:
        new_state=handler.changeNumber(new_state,action.payload.numType,action.payload.num)
        break;
        case COMPUTE:
        new_state=handler.compute(new_state)
        break;
        default:
            break;
    }
    return new_state
}
export default reducer


const handler={
    changeNumber(state,numType,num){    //new_state={pervNumber,nextNumber}
        state[numType]=num
        return state
    },
    compute(state){
        state["result"]=state.prevNumber*1+state.nextNumber*1
        return state
    }
}

src/store/caculator/actionType.js

export const CHANGE_NUMBER="CHANGE_NUMBER"
export const COMPUTE="COMPUTE"

src/store/caculator/actionCreator.js

import {CHANGE_NUMBER,COMPUTE}  from "./actionType"

export default{
    changeNumber(numType,num){  //这些方法成为actionCreators
        let action={
            type:CHANGE_NUMBER,
            payload:{
                numType,
                num
            }
        }
        return action
    },
    compute(){
        let action={
            type:COMPUTE
        }
        return action
    }
}

src/group/caculator-group.js

import {connect} from "react-redux"
import actionCreators from "../calculator/actionCreators"
export default connect(state=>state.calculator,actionCreators)

3.组件

TodoList组件

src/components/todolist/index.js

import React, { Component } from 'react'
import TodoInput from "./TodoInput"
import TodoContent from "./TodoContent"
import TodoInfo from "./TodoInfo"

export default class index extends Component {
    render() {
        return (
            <div>
                <TodoInput></TodoInput>
                <TodoContent></TodoContent>
                <TodoInfo></TodoInfo>
            </div>
        )
    }
}

src/components/todolist/TodoContent.js

import React, { Component } from 'react'
import actionCreators from "../../store/todolist/actionCreators"
import { connect } from "react-redux"

//函数式组件(无状态组件)
const LiItem = props => {
    const handleChange = () => {
        //需要更改redux的状态,所以应该派发action进行更改
        props.pp.changeNewTodo(props.item.id)
    }
    const handleRemove = () => {
        props.pp.removeNewTodo(props.item.id)
    }
    return (
        <li>
            <input type="checkbox" onChange={handleChange} checked={props.item.isFinished} />
            {props.item.title}
            <button onClick={handleRemove}>删除</button>
        </li>
    )
}

class TodoContent extends Component {
    renderItem() {
        // console.log(this.props)  //{todos,三个更改状态的方法}  {todos: Array(2), addNewTodo: ƒ, changeNewTodo: ƒ, removeNewTodo: ƒ}
        let { todos } = this.props
        return (
            todos.map(item => {
                return (
                    <LiItem item={item} key={item.id} pp={this.props} />
                )
            })
        )
    }

    render() {
        return (
            <div>
                <ul>
                    {this.renderItem()}
                </ul>
            </div>
        )
    }
}

//connect(mapStateToProps,mapDispatchToProps)(UIComponent)
//mapStateToProps是一个函数,这个函数参数是state,可以让UIComponent通过this.props获取到redux的状态。
//是一个函数,这个函数参数是dispatch,可以让UIComponent通过this.props获取到更改redux状态的actionCreators方法。

const mapStateToProps = state => {
    return state.todolist
}

const mapDispatchToProps = actionCreators

export default connect(mapStateToProps, mapDispatchToProps)(TodoContent)

src/components/todolist/TodoInfo.js

import React, { Component } from 'react'
import {connect} from "react-redux"

class TodoInfo extends Component {
    computed() {
        let { todos } = this.props
        let all = { total: 0, finish: 0, unfinish: 0 }
        if (todos.length === 0) return all;
        all.total = todos.length
        todos.forEach(item => {
            if (item.isFinished) {
                all.finish++
            } else {
                all.unfinish++
            }
        })
        return all
    }

    //这个钩子函数只有当外部传入进来的属性发生变化的时候,此钩子函数才会执行
    UNSAFE_componentWillReceiveProps(){
        console.log(111111)
    }

    render() {
        let all = this.computed()
        return (
            <div>
                <span>总共{all.total}</span>
                <span>完成了{all.finish}</span>
                <span>未完成{all.unfinish}</span>
            </div>
        )
    }
}


//connect()(UI组件/傻瓜组件木偶组件/展示组件)===>返回一个容器组件/智能组件
//connect() 是一个HOC高阶组件,本质上就是一个函数,可以接收一个UI组件,返回一个新的组件   /shouldComponent  PureComponent React.memo(组件)
//这个函数返回什么,TodoInfo这个组件的属性上面就会有什么
//我们发现参数上面的state其实就是store.getState()之后的结果
//其实当redux的状态发生变化的时候,容器组件可以监听到状态的变化,然后把最新的状态通过属性的方式传递给了TodoInfo组件
//因为容器组件内部已经帮助我们实现了store.subscribe方法的订阅了
const mapStateToProps=state=>{
    return state.todolist
}

export default connect(mapStateToProps)(TodoInfo)

src/components/todolist/TodoInput

import React, { Component, createRef } from 'react'
import actionCreators from "../../store/todolist/actionCreators"
import { connect } from 'react-redux'

class TodoInput extends Component {
    constructor() {
        super()
        this.input = createRef()
    }
    handleKeyUp = (e) => {
        if (e.keyCode === 13) {
            //通过actionCreators创建action派发给reducer进行处理
            this.props.addNewTodo(this.input.current.value)
            this.input.current.value = ""
        }
    }
    render() {
        return (
            <div>
                <input ref={this.input} type="text" onKeyUp={this.handleKeyUp} />
            </div>
        )
    }
}

//这个方法返回什么,UI组件的属性上面就会有什么
//方法作用就是可以在这里面挂载一些更改redux状态的方法,UI组件可以通过属性去访问到
// const mapDispatchToProps=dispath=>{
//     return {
//         addNewTodo:title=>{
//             let action=actionCreators.addNewTodo(title)
//             dispath(action)
//         }
//     }
// }

//将所有的更改redux的状态的方法全都挂载到UI组件的属性上面去,并且内部会自动的将action派发给reducer
const mapDispatchToProps = actionCreators
export default connect(null,mapDispatchToProps)(TodoInput)

Calculator组件

src/components/calculator/index.js

import React, { Component } from 'react'
import Expression from "./Expression"
import Result from "./Result"

export default class index extends Component {
    render() {
        return (
            <div>
                <Expression></Expression>
                =
                <Result></Result>
            </div>
        )
    }
}

src/components/calculator/Result.js

import React, { Component } from 'react'
import CalculatorGroup from "../../store/group/calculator-group"

class Result extends Component {
    render() {
        return (
        <button>{this.props.result}</button>
        )
    }
}

export default CalculatorGroup(Result)

src/components/calculator/Expression.js

import React, { Component, Fragment } from 'react'
import CalculatorGroup from "../../store/group/calculator-group"

class Expression extends Component {
    handleChange=(e)=>{
        // console.log(e.target.id,e.target.value)
        this.props.changeNumber(e.target.id,e.target.value)
    }

    compute=()=>{
        this.props.compute()
    }

    render() {
        let {prevNumber,nextNumber,operator}=this.props
        return (
            <Fragment>
                <input id="prevNumber" onChange={this.handleChange} type="text" value={prevNumber} />
        <button onClick={this.compute}>{operator}</button>
                <input id="nextNumber" onChange={this.handleChange} type="text" value={nextNumber}/>
            </Fragment>
        )
    }
}

export default CalculatorGroup(Expression)

//Expression组件上面就可以通过this.props获取到redux状态与更改状态的所有的方法了
//connect(state=>state.calculator,actionCerators)(Expression)

redux-thunk 中间件

使用方式: npm i redux-thunk
可以在action里进行异步操作
需要更改我们 store/index.js文件

import thunk from "redux-thunk"
const store = createStore(reducer,applyMiddleware(thunk))


  • 内部实现原理
function createThunkMiddleware(extraArgument) {
  return ({ dispatch, getState }) => (next) => (action) => {
    if (typeof action === 'function') {
      return action(dispatch, getState, extraArgument);
    }

    return next(action);
  };
}

const thunk = createThunkMiddleware();
thunk.withExtraArgument = createThunkMiddleware;

export default thunk;
  • 核心代码
    判断是否是一个函数,如果是的话进行异步操作
if (typeof action === 'function') {
return action(dispatch, getState, extraArgument);
}

return next(action);

实现效果

Redux——结合案例一步步深入学习_第7张图片

实现思想

src/store/index.js

import {createStore, applyMiddleware} from "redux"
import reducer from "./reducer"  
import thunk from "redux-thunk"
//第一个参数是合并的reducer
//第二个参数是使用中间件
const store = createStore(reducer,applyMiddleware(thunk))
export default store

src/store/calculator/actionCreators.js

import {CHANGE_NUMBER,COMPUTE}  from "./actionType"

export default{
    changeNumber(numType,num){  //这些方法成为actionCreators
        let action={    //定义了具有标志信息的action对象,然后将这个action传递给了redux-thunk中间件
            type:CHANGE_NUMBER,
            payload:{
                numType,
                num
            }
        }
        return action
    },
    // compute(){  //这个是前端点击+号按钮的时候触发的方法
    //    setTimeout(()=>{
    //     let action={
    //         type:COMPUTE
    //     }
    //     return action
    //    },1000)
    // }

    /*
        我们在redux的actionCreators里面智能做一些同步操作,但是后续如果有异步需求,例如ajas操作,它就实现不了
        我们只能通过安装redux的一些中间件帮助实现我们的异步操作。redux-thunk redux-sage redux-promise

        redux的中间件?  actionCreators创建的action到达reducer中间的过程
        之前的同步操作
        actionCreators ===> 自动的调用dispatch(action) ===> reducer ===> 返回新状态给store ===> react组件view
        安装中间件之后的异步操作
        actionCreators ===> middleware处理后 ===> 手动调用dispatch(action) ===> reducer ===> 返回新状态给store ===> react组件view

        redux-thunk 源码就会根据 actionCreators的return是一个对象还是一个函数?
        如果是对象的话,那么内部就会自动的派发action的reducer进行处理(同步)
        如果是函数的话,那么内部就可以提供一个参数dispatch给你,让你自己选择对应的时机进行dispatch(action)给reducer处理


        redux-thunk 其实就是增强了派发功能。 store.dispatch(action)的增强
    */

    //dispatch 可以更换
    compute(){
        return dispatch=>{ //这里的actionCreators返回的是一个函数,那么交给redux-thunk处理  action(dispatch)
            setTimeout(()=>{ //如果这里采用函数的写法,那么内部就可以写一些异步业务逻辑了。
                dispatch({type:COMPUTE})
            },1000)
        }
    }
}

redux使用总结(以TodoList案例为例)

安装npm i redux react-redux redux-thunk

1.创建redux管理,reducer的派发,

src/store/todolist/state.js

export default {
    todos:[
        {id:1,title:"今天周一",isFinished:false},
        {id:2,title:"检查作业",isFinished:true}
    ]
}

src/store/todolist/reducer.js

//reducer必须是一个纯函数
//固定的输入必须要有固定的输出,内部函数不能有不纯粹的操作 Math.random()  new Date()
//不能对之前的状态进行任何的更改 内部必须是同步的
//redux思想  状态与视图是一一对应的

//新的状态与之前的状态通过引用地址进行比较的,如果操作的新状态的地址与原状态一样,内部就会认为没有返回新的状态,那么视图也不会有新的变化
import state from "./state"
import {ADD_NEW_TODO,REMOVE_NEW_TODO,CHANGE_NEW_TODO} from "./actionType"
const reducer = (prevState = state,action)=>{  //prevState={todos:[]}
    let new_state={...prevState}  //new_state={todos:[]}    浅拷贝 {...}object.assign vs  深拷贝  (递归, 对象  JSON stringify  parse)
    switch(action.type){
        case ADD_NEW_TODO: 
            new_state.todos=new_state.todos.slice()  //将之前的状态拷贝出来新的一份地址不一样
            new_state.todos.push({id:handler.getId(new_state.todos),title:action.title,isFinished:false})
            break;
        case CHANGE_NEW_TODO:
            new_state.todos=handler.changeNewTodo(new_state.todos,action.id)
            break;
        case REMOVE_NEW_TODO:
            new_state.todos=handler.removeNewTodo(new_state.todos,action.id)
            break;
        default:
            break;
    }
    return new_state   //通过store.getState()其实就是获取到了reducer返回的内容
}
export default reducer


const handler={
    getId(todos){
        todos=todos.slice()
        if(todos.length === 0) return 1;
        return todos.sort((a,b)=>{
            return b.id-a.id
        })[0].id+1
    },
    changeNewTodo(todos,id){
        todos=todos.slice()
        for(var i=0;i<todos.length;i++){
            if(todos[i].id===id){  //正是想要更改的这一项
                todos[i].isFinished=!todos[i].isFinished
            }
        }
        return todos
    },
    removeNewTodo(todos,id){
        todos=todos.slice()
        return todos.filter(item=> item.id!==id)
    }
}

src/store/todolist/actionType.js

import {ADD_NEW_TODO,REMOVE_NEW_TODO,CHANGE_NEW_TODO} from "./actionType"
export default{   //面试题?   actionCreators or action 关系?   actionCreators创建action    action 一般是一个对象  type 通常写为type
    addNewTodo(title){
        let action={ //一般的action是具有标志性的信息的对象
            type:ADD_NEW_TODO,
            title
        }
        //需要将action给reducer传递过去
        return action
    },
    changeNewTodo(id){
        let action={
            type:CHANGE_NEW_TODO,
            id
        }
        return action
    },
    removeNewTodo(id){
        let action={
            type:REMOVE_NEW_TODO,
            id
        }
        return action
    }
}


src/store/todolist/actionCreators

import {ADD_NEW_TODO,REMOVE_NEW_TODO,CHANGE_NEW_TODO} from "./actionType"
export default{   //面试题?   actionCreators or action 关系?   actionCreators创建action    action 一般是一个对象  type 通常写为type
    addNewTodo(title){
        let action={ //一般的action是具有标志性的信息的对象
            type:ADD_NEW_TODO,
            title
        }
        //需要将action给reducer传递过去
        return action
    },
    changeNewTodo(id){
        let action={
            type:CHANGE_NEW_TODO,
            id
        }
        return action
    },
    removeNewTodo(id){
        let action={
            type:REMOVE_NEW_TODO,
            id
        }
        return action
    }
}


2.在组件中的使用,react-redux的使用

  • Provider包裹组件,添加store属性

src/index.js
引入import store from "./store"import {Provider} from "react-redux"
Provider包裹组件,添加store属性

import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';
import store from "./store"
import {Provider} from "react-redux"

ReactDOM.render(
  //这边通过store属性传下去,那么所有的Provider的后代组件都可以通过connect连接,然后获取redux的状态了
   <Provider store={store}>
     <App/>
   </Provider>
  ,
  document.getElementById('root')
);

  • 利用connect()高阶组件,将redux的共享状态和方法可以通过属性访问得到并使用

src/components/todolist/TodoCotent.js

import React, { Component } from 'react'
import actionCreators from "../../store/todolist/actionCreators"
import { connect } from "react-redux"

//函数式组件(无状态组件)
const LiItem = props => {
    const handleChange = () => {
        //需要更改redux的状态,所以应该派发action进行更改
        props.pp.changeNewTodo(props.item.id)
    }
    const handleRemove = () => {
        props.pp.removeNewTodo(props.item.id)
    }
    return (
        <li>
            <input type="checkbox" onChange={handleChange} checked={props.item.isFinished} />
            {props.item.title}
            <button onClick={handleRemove}>删除</button>
        </li>
    )
}

class TodoContent extends Component {
    renderItem() {
        // console.log(this.props)  //{todos,三个更改状态的方法}  {todos: Array(2), addNewTodo: ƒ, changeNewTodo: ƒ, removeNewTodo: ƒ}
        let { todos } = this.props
        return (
            todos.map(item => {
                return (
                    <LiItem item={item} key={item.id} pp={this.props} />
                )
            })
        )
    }

    render() {
        return (
            <div>
                <ul>
                    {this.renderItem()}
                </ul>
            </div>
        )
    }
}

//connect(mapStateToProps,mapDispatchToProps)(UIComponent)
//mapStateToProps是一个函数,这个函数参数是state,可以让UIComponent通过this.props获取到redux的状态。
//是一个函数,这个函数参数是dispatch,可以让UIComponent通过this.props获取到更改redux状态的actionCreators方法。

const mapStateToProps = state => {
    return state.todolist
}

const mapDispatchToProps = actionCreators

export default connect(mapStateToProps, mapDispatchToProps)(TodoContent)

3.redux-thunk可以进行异步操作

src/store/calculator/actionCreators.js

 //dispatch 可以更换
    compute(){
        return dispatch=>{ //这里的actionCreators返回的是一个函数,那么交给redux-thunk处理  action(dispatch)
            setTimeout(()=>{ //如果这里采用函数的写法,那么内部就可以写一些异步业务逻辑了。
                dispatch({type:COMPUTE})
            },1000)
        }
    }

你可能感兴趣的:(React,javascript,前端,react,redux)