bunny笔记|React学习-Redux以及其相关插件的理解与

2022-04-12  本文已影响0人  一只小小小bunny

Redux适用于中大型项目开发进行统一的数据管理和维护。Redux独立与组件树,专门用于对数据的统一管理的集合。

Redux=Reducer + Flux
Flux是React原始用于数据管理的,由于存在多个store的问题,最终升级成Redux

Redux的工作流:
React Components -> Action Creators (转发action )-> Store (传入state和action)Action Creators ->(得到newState) Store ->(State变化重新渲染) ->React Components

redux.png

用Redux改造todoList案例
1)安装 yarn add redux (package.json中查看是否生成)
2)创建store index.js actionCreators.js actionType.js reducer.js

import { createStore } from 'redux';
import reducer from './reducer';

//实例化store
const store = createStore(reducer, window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__());
export default store;
//默认数据
const defaultState = {
    todos: [
        { id: 1, title: '学习React课程', finished: false },
        { id: 2, title: '刷半小时电视剧', finished: false },
        { id: 3, title: '学习Java的课程', finished: false },
        { id: 4, title: '学习Python的课程', finished: false },
    ],
    finishedCount: 0
}

export default (state = defaultState, action) => {
    console.log(state, action);
    return state
}

List.jsx

import store from '../store'
export default class List extends Component {
    constructor(props){
        super(props);
        //获取redux中的数据
        this.state = store.getState()
    }
    
    //...Other Part
}
    render() {
        console.log(this.state);
        const{todos}=this.state;
        //...Other Part
    }
删除redux里的数据实现删除功能

注意:getDelItemAction封装数据 固定写法:export const 变量名=()=>({})

actionTypes.js

export const DEL_TODO_ITEM ='del_todo_item';//删除一条记录

actionCreators.js

import { DEL_TODO_ITEM } from "./actionTypes"

//1.删除一条记录
//getDelItemAction封装数据 固定写法:export const 变量名=()=>({})
export const getDelItemAction=(todoId)=>({
    type:DEL_TODO_ITEM,//要做什么事情,要做删除处理
    todoId //根据todoId做数据删除

})

reducer.js

import { DEL_TODO_ITEM } from "./actionTypes"

//默认数据
const defaultState = {
    todos: [
        { id: 1, title: '学习React课程', finished: false },
        { id: 2, title: '刷半小时电视剧', finished: false },
        { id: 3, title: '学习Java的课程', finished: false },
        { id: 4, title: '学习Python的课程', finished: false },
    ],
    finishedCount: 0
}

export default (state = defaultState, action) => {
    console.log(state, action);
    //1.删除一条todo
    if (action.type === DEL_TODO_ITEM) {
        //深拷贝(原数据)
        const newState = JSON.parse(JSON.stringify(state));
        // 1.1 遍历
        let finishedCount = 0;
        newState.todos.forEach((todo, index) => {
            if (todo.id === action.todoId) {
                newState.todos.splice(index, 1);
            }
        });

        // 1.2 处理选中的
        newState.todos.forEach((todo, index) => {
            if (todo.finished) {
                finishedCount += 1;
            }
        });

        // 1.3 更新状态
        newState.finishedCount = finishedCount;
        return newState;

    }
    return state
}
import React, {Component} from 'react';
import Item from './Item';
import store from './../store';

export default class List extends Component {
    constructor(props){
        super(props);
        // 获取redux中的数据
        this.state = store.getState();
        // 订阅store的改变
        this._handleStoreChange = this._handleStoreChange.bind(this);
        store.subscribe(this._handleStoreChange);
    }

    render() {
        const {todos} = this.state;
        return (
            <ul className="todo-main">
                {
                    todos.map((todo, index) => (
                        <Item
                            key={index}
                            todo={todo}
                        />
                    ))
                }
            </ul>
        )
    }

    //更新Store
    _handleStoreChange(){
       // 更新状态
       this.setState(store.getState())
    }
}

综上,删除一条记录的方法已经redux的处理完成了。

store目录下即redux的文件
1)index.js 入口
2)actionCreators.js 定义行为和方法,把需要的数据传到reducer.js
3)reducer.js的action接收数据后对数据处理,实现逻辑功能
4)actionCreators.js 则是做为常量类型声明的管理集合

    onClick={()=>this._dealRemove(todo.id)}

并将该方法的实现在render()方法后面添加处理

Item.jsx

import store from '../store';
import {getDelItemAction} from '../store/actionCreators' 

//删除一条记录
_dealRemove(todoId){
    //拿到action
    const action =getDelItemAction(todoId);
    //固定写法。把action分发(传)下去
    store.dispatch(action);
}
export default class List extends Component {

    constructor(props){
        super(props);
        //获取redux中的数据
        this.state = store.getState();

        this._handeleStoreChange=this._handeleStoreChange.bind(this)
        //订阅store的改变
        store.subscribe(this._handeleStoreChange)

    }
    
    //...Other Part
    //在render()方法的下面实现该监听处理的方法
    
    _handeleStoreChange(){
        //更新状态,进行页面的重新绘制
        this.setState(store.getState())
    }
}
总结:
  • 第一步 把数据都抽到store。
    store/index.js是统一对外的,所以这里要输出一个store
    调用createStore()的方法把reducer关联起来
  • 第二步 所有操作数据和状态的方法(操作action的方法)都在actionCreators.js统一封装处理
    将actionCreators.js与actionTypes.js做关联(常量声明处理)
  • 第三步 界面调用。怎么调用,主要是Component下的jsx文件
    这里是Item.jsx和List.jsx

能结合redux第三方工具 更好理解一些

redux02.jpg
redux (2).png
选中redux里的数据条实现选中功能

第一步 先进入store/index.js,但是这里不需要做其它改变了
界面是触发一个action,然后通过Store用diaspatch()派发action给到reducer。然后reducer来改变最终的数据返回给我们的index.js,然后index.js再返回出来store。(终结者是store)

第二步 修改actionCreators.js和actionTypes.js

第三步 修改List.jsx和Item.jsx

其它处理可参考如上所述的处理流程。

以下是redux处理方法部分的源码(即store目录下的源码,全部源码可以到git下clone获取):

index.js

import { createStore } from 'redux';
import reducer from './reducer';

//实例化store
const store = createStore(reducer, window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__());
export default store;

actionTypes.js

//常量声明是为了防止方法报错,有利于代码的健壮
export const DEL_TODO_ITEM ='del_todo_item';//删除一条记录
export const CHANGE_TODO_ITEM = 'change_todo_item'; // 修改一条记录的转态
export const ADD_TODO_ITEM = 'add_todo_item'; // 添加一条记录
export const REMOVE_FINISHED_TODO_ITEM = 'remove_finished_todo_item'; // 删除已经完成的所有任务
export const IS_CHECKED_ALL_TODO_ITEM = 'is_checked_all_todo_item'; // 全选和非全选
export const GET_ALL_ITEM = 'get_all_item'; // 获取所有记录

actionCreators.js

import {
    DEL_TODO_ITEM, 
    CHANGE_TODO_ITEM,
    ADD_TODO_ITEM,
    REMOVE_FINISHED_TODO_ITEM,
    IS_CHECKED_ALL_TODO_ITEM,
    GET_ALL_ITEM
} from "./actionTypes"

//1.删除一条记录
//getDelItemAction封装数据 固定写法:export const 变量名=()=>({})
export const getDelItemAction = (todoId) => ({
    type: DEL_TODO_ITEM,
    todoId

})
// 2. 修改一条记录的状态
export const getChangeItemFinishedAction = (todoId, isFinished) => ({
    type: CHANGE_TODO_ITEM,
    todoId,
    isFinished
});

// 3. 添加一条记录
export const getAddItemAction = (todo) => ({
    type: ADD_TODO_ITEM,
    todo
});

// 4. 删除已经完成的所有任务
export const getRemoveFinishedItemAction = () => ({
    type: REMOVE_FINISHED_TODO_ITEM
});

// 5. 全选和非全选
export const getIsCheckedAll = (flag) => ({
    type: IS_CHECKED_ALL_TODO_ITEM,
    flag
});

reducer.js

import {
    DEL_TODO_ITEM,
    CHANGE_TODO_ITEM,
    ADD_TODO_ITEM,
    REMOVE_FINISHED_TODO_ITEM,
    IS_CHECKED_ALL_TODO_ITEM
} from './actionTypes'

//默认数据
const defaultState = {
    todos: [
        { id: 1, title: '学习React课程', finished: false },
        { id: 2, title: '刷半小时电视剧', finished: false },
        { id: 3, title: '学习Java的课程', finished: false },
        { id: 4, title: '学习Python的课程', finished: false },
    ],
    finishedCount: 0
}

export default (state = defaultState, action) => {
    console.log(state, action);
    //1.删除一条todo
    if (action.type === DEL_TODO_ITEM) {
        //深拷贝(原数据)
        const newState = JSON.parse(JSON.stringify(state));
        // 1.1 遍历
        let finishedCount = 0;
        newState.todos.forEach((todo, index) => {
            if (todo.id === action.todoId) {
                newState.todos.splice(index, 1);
            }
        });

        // 1.2 处理选中的
        newState.todos.forEach((todo, index) => {
            if (todo.finished) {
                finishedCount += 1;
            }
        });

        // 1.3 更新状态
        newState.finishedCount = finishedCount;
        return newState;

    }
    // 2. 修改一条记录的状态
    if(action.type === CHANGE_TODO_ITEM){
        // 1.1 遍历
        const newState = JSON.parse(JSON.stringify(state));
        let finishedCount = 0;
        newState.todos.forEach((todo, index) => {
            if (todo.id === action.todoId) {
                todo.finished = action.isFinished;
            }
            if (todo.finished) {
                finishedCount += 1;
            }
        });

        // 2.3 返回新的数据状态
        newState.finishedCount = finishedCount;
        return newState;
    }
    // 3. 添加一条记录
    if(action.type === ADD_TODO_ITEM){
        const newState = JSON.parse(JSON.stringify(state));
        newState.todos.push(action.todo);
        return newState;
    }
    // 4. 删除已经完成的所有任务
    if(action.type === REMOVE_FINISHED_TODO_ITEM){
        const newState = JSON.parse(JSON.stringify(state));
        // 4.1 取出对象
        let tempArr = [];
        newState.todos.forEach((todo, index) => {
            if (!todo.finished) {
                tempArr.push(todo);
            }
        });
        // 4.2 返回最新的状态
        newState.todos = tempArr;
        newState.finishedCount = 0;
        return newState;
    }
    // 5. 全选和非全选
    if(action.type === IS_CHECKED_ALL_TODO_ITEM){
        const newState = JSON.parse(JSON.stringify(state));
        // 5.1 遍历
        let finishedCount = 0;
        newState.todos.forEach((todo, index) => {
            todo.finished = action.flag;
        });

        // 5.2 处理选中的
        newState.todos.forEach((todo, index) => {
            if (todo.finished) {
                finishedCount += 1;
            }
        });

        // 5.3 更新状态
        newState.finishedCount = finishedCount;
        return newState;
    }
    return state
}

网络异步

1)服务器端
1.搭建node的express服务器
下载node
全局安装express npm install express -g
创建服务器
express --view=ejs react-api
cd react-api
npm install
运行服务器npm run

此时共有两个文件目录:
Promise(用于前端请求接口)
react-api(基于node的后端服务器,提供接口和方法) 可进入bin目录下的www,将端口号修改为'5207',以防与3000冲突。

var port = normalizePort(process.env.PORT || '5207');
app.set('port', port);

这里的意思是要从环境中获取到端口,并存储于Express中以供使用。

index.js 入口文件
ajax.js 请求接口的文件

npm install axios或者yarn add axios安装axios

api/ajax.js

import axios from 'axios'

export default function ajax(url = '', params = {}, type= 'GET') {
     let promise;
     // 1. 返回promise对象
     return new Promise((resolve, reject)=>{
          // 1.1 判断请求的方式
          if('GET' === type.toUpperCase()){
              // 1.2 拼接字符串
              let paramsStr = '';
              Object.keys(params).forEach(key =>{
                 paramsStr += key + '=' + params[key] + '&'
              });
              // 1.3 过滤最后的&
              if(paramsStr !== ''){
                  paramsStr = paramsStr.substr(0, paramsStr.lastIndexOf('&'));
              }
              // 1.4 拼接完整的路径
              url += '?' + paramsStr;
              // 1.5 发起get请求
              promise = axios.get(url);
          }else if('POST' === type.toUpperCase()){
              promise = axios.post(url, params);
          }
          // 1.6 返回结果
         promise.then((response)=>{
             resolve(response.data);
         }).catch(error=>{
             reject(error);
         })
     })
}

api/index.js

import ajax from './ajax'

// 0. 定义基础路径
const BASE_URL = '/api';

// 1. 请求todo列表
export const getTodoList = () => ajax(BASE_URL + '/todos');

2)客户端

1.基于axios封装get/post
2)客户端 2.通过async和await接收promise
3.界面交互,最终把数据存入store中


redux-saga 和 redux-thunk

1)redux-thunk

概念:redux-thunk是一个redux的中间件,用来处理redux中的复杂逻辑,比如异步请求;
redux-thunk中间件可以让action创建函数不仅仅返回一个action对象,也可以是返回一个函数:
(说明:redux-dev-tools和redux-thunk兼容)

2)redux-saga

1.概念:
redux-saga是一个用于管理redux应用异步操作的中间件,redux saga通过创建sagas将所有异步操作逻辑收集在一个地方集中处理,可以用来代替redux-thunk中间件。

原理:

  1. sagas是通过generator函数来创建的
    Redux中间件
  2. sagas监听发起的action,然后决定基于这个action来做什么
    3)在redux-saga中,所有的任务都通过用yield Effects来完成
    4)redux-saga为各项任务提供了各种Effects创建器,让我们可以用同步的方式来写异步代码

安装:npm install redux-sagayarn add redux-saga

react-redux

Why? 避免Redux中store全局化,把store直接集成到React应用的顶层 props里面,方便各个子组件能访问到顶层props。解决手动监听state中数据改变store.subscribe(render)

核心 Provider和connect方法
Provider 将顶层组件包裹在Provider组件之中,所有组件就处于react-redux的控制之下了,store作为参数放到Provider组件中,方便其中所有子组件调用。

结构 <Provider>

React-Redux 1)Provider <Provider store = {store)> 
<组件 />

目的 使得所有组件都能够访问到Redux中的数据

语法:
connect(mapStateToProps,mapDispatchToProps)(MyComponent)

mapStateToProps 把state映射到props中去,意思就是把Redux中的数据映射到React中的props中去。
映射:

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

渲染:this.props.todos

目的:把Redux中的state变成React中的props

mapDispatchToProps
把各种dispatch也变成了props,在组件中可以直接使用。
mapDispatchToProps。避免了手动去用storesubscribe订阅render函数以达到更新页面的目的

上一篇 下一篇

猜你喜欢

热点阅读