【React阶段二 分支一 Redux】1、Redux模式& Antd(技术胖)

371 阅读10分钟

一、实践笔记来自:技术胖-Redux免费视频教程(24集)

知识体系三:

文章定位:Redux模式了解,很多写法已经过时,【前端笔记】五、Redux Toolkit概述 & 官方模板,axios异步请求还是蛮重要的。

3620b82161005a0a1133b570f2bc6ef8.png

  • 基本流程

978623effd3eeaefd63a8faef5bf2eb6.png

第3节 Redux及Antd初始化

1、 先到目标文件夹建立一个Redux应用,项目名必须要小写

npx create-react-app reduxdemo

2、准备写一个TodoList组件,保留index.js;其他都可以先删了

image.png

  • index.js对应没啥用的也删光

3、TodoList类创建与使用

  1. TodoList本体
import React, { Component } from 'react';

class TodoList extends Component {
    state = {  } 
    render() { 
        return(
            <div>Hello World</div>
        )

    }
}
 
export default TodoList;
  1. 调用
import React from 'react';
import ReactDOM from 'react-dom/client';
import TodoList from './TodoList';


const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
  <React.StrictMode>
    <TodoList />
  </React.StrictMode>
);

  1. 引入antd
  • npm install antd --save
  • 网速不好的可以用yarn add antd

第四节 Antd美化

1、整体UI类似笔记2,使用ANTD美化

  • import 'antd/dist/antd.css' 貌似没快捷键,新版本会报错说找不到这个文件,从modules里找发现改成了 import 'antd/dist/reset.css';
  • antd包下的Input I大写
  • 其中style需要双括号:style={{width:"250px"} },多个属性期间用逗号隔开
class TodoList extends Component {
    state = {  } 
    render() { 
        return(
            <div>
                <Input 
                placeholder='Free Hug'   
                style={{width:"250px"} }/> 
        
            </div>
        )

    }
}

2、 引入 Input,Button,List

  • 支持const定义数据
import React, { Component } from 'react';
import 'antd/dist/reset.css';
import { Input,Button,List} from 'antd';
import Item from 'antd/es/list/Item';

const data=[
    '香菇滑鸡',
    '鱼香肉丝',
    '蒸羊羔'
]

class TodoList extends Component {
    state = {  } 
    render() { 
        return(
            <div>
            {/* 最外层Div加入样式  增加外边距 */}
            <div  style={{margin:'20px'}}>
                <Input 
                placeholder='Free Hug'   
                style={{width:"250px",marginRight:"10px"} } //直接在这边加右边距省的button写style
                /> 
                <Button type='primary' >增加</Button>
            </div>
            <div 
            style={{maigin:'10px ', width:'300px'}}
            >
                <List
                bordered
                dataSource={data}
                /* ES6语法糖解析  antd规范*/
                renderItem={Item=>(
                    <List.Item>
                            {Item}
                    </List.Item>
                )}
                />
            </div>
            </div>
        )

    }
}
 
export default TodoList;

image.png

第五节 创建Redux中的仓库-store和Reducer

image.png

  • Store统一管理状态
  • Action为每个组件发起的事件,reducer通过store传递来的action返回新的state;
  • 组件通过subscribe更新新的state;

1、 创建最重要的store

*npm install --save redux

2、src下建立store文件夹

  1. 文件夹下建立index.js作为管理文件
  • 导包 import { createStore } from "redux";被弃用,使用import {legacy_createStore as createStore} from 'redux'
import {legacy_createStore as createStore} from 'redux'

const store =createStore()

/* 暴露出去 */
export default store

  1. 包下建立reducer.js
  • 提供管理状态的方法
//提供方法 管理状态 使用匿名函数返回状态

//定义个默认参数
const defaultState={}

export default(state=defaultState,action)=>{
    return state
}
  1. store内index.js中createStore注入管理了类reducer
import {legacy_createStore as createStore} from 'redux'
import reducer from './reducer'

/* 引入reduceer后  方法注入 */
const store =createStore(reducer)

/* 暴露出去 */
export default store

  1. 接下来将TodoList的const数据迁移到reducer.js中,通过其方法进行获取
  2. 定义两个Value
  • inputValue:用于placeholder占位
  • list:用于每个list展示
//提供方法 管理状态 使用匿名函数返回状态

//定义个默认参数
const defaultState={
    inputValue:'sport menu',
    list:[
        'swim',
        'running',
        'jump'
    ]
}

export default(state=defaultState,action)=>{
    return state
}
  1. TodoList中在构造方法里联动store
  constructor(props){
        super((props))
        console.log(store.getState())
    }

image.png

  1. 值已经通过引入store,通过其注入的reducer类(exprot default)获取,接下来应该将其与TodoList 构造函数中的state联动
    constructor(props){
        super((props))
        //console.log(store.getState())
        this.state=store.getState()
    }
  • 顺道把input的placeholder改成从state里取 placeholder={this.state.inputValue}
             <List
                bordered /* 加这个会好看点,有外边框 */
                dataSource={this.state.list}
                /* ES6语法糖解析  antd规范*/
                renderItem={Item=>(
                    <List.Item>
                            {Item}
                    </List.Item>
                )}
                />
image.png

第六节 Redux Dev Tools的安装

  • 可以在控制台中调试数据
  • 安装好之后需要配置createStore参数
  • 配置官网 Redux DevTools
/* 引入reduceer后  方法注入 */
const store =createStore(
    reducer,
    window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__() /* 通过有这个插件就调用 */
    )

image.png

第七节 通过Input体验Redux的流程

1、和以前一样 定义方法changeInputValue,标签使用onChange={this.changeInputValue} 构造函数对方法的this进行bind绑定

2、我们的目的是Action要改变store中的值来驱动UI层变化,TodoList中操作

  1. Action在代码表现上是个对象,在对应方法里定义就可以
  • 必须要指定type和value
  1. type有了之后需要通过dispatch将其关联回store ,store因为有动态注入,此时调用分发方法dispatch即可传给action,驱动数据更新;reducer会自动传值
    changeInputValue(el){
        const action={
            type:'changeInput', /* 命名,如果提示波浪线 一般都是:没写或者写成=了 */
            value:el.target.value
        }
        /* 建立好之后 需要通过dispatch联系发送   */
        store.dispatch(action)
    }
  1. reducer把他的值打出来,列表中看到每次有两行,第一行是源数据,第二行是传递的action,有type和value两个属性。

// 俩参数 1是数据源 2是接受的数据
export default(state=defaultState,action)=>{
    console.log(state,action)
    return state
}

image.png

  • 此时通过辅助工具查看inputValue值是没变化的(先输入值,然后删光,发现state变了,但UI中inputValue没变)

image.png

  • 订阅方法:先输入再删光后UI里的也变了,不订阅视图不会更新
   constructor(props) {
        super((props))
        //console.log(store.getState())
        this.state = store.getState()
        this.changeInputValue = this.changeInputValue.bind(this)

        this.storeChange = this.storeChange.bind(this)  //转变this指向
        store.subscribe(this.storeChange) //订阅Redux的状态
    }
    
    
       storeChange(){
        this.setState(store.getState)
    }

image.png

  • 关于setState,初始时以es6方式定义了类数组的state,使用setState若里面有旧的key,则对应key的值会更新;若有新的key,新的key与对应值会加入到state中;
  1. 控制对应的reducer数据更新
  • 针对action.type做特化处理,驱动数据跟新反作给store
  • 保持数据源的完整性
  • 新版本不用主动subscribe
// 俩参数 1是数据源 2是接受的数据
export default(state=defaultState,action)=>{
    console.log(state,action)
    //reducer不能污染数据源,针对特点action进行特化数据驱动
    if(action.type === 'changeInput'){
        let newState = JSON.parse(JSON.stringify(state)) //深度拷贝state
        newState.inputValue = action.value
        return newState
    }
    return state
}
  • 此时发现第五节改写的从stateinput的值已经是么修改后的action的type了,完成reducer驱动

image.png

image.png

第八节 通过Input制作TodoList功能

  • imput修改后,可以制作add功能 1、 增加点击事件 <Button type='primary' onClick={this.clickBtn}>增加</Button> 2、 处理方法
  1. 方法体设置type,给Reducer判断
  2. 通过dispatch下发回去
   clickBtn(){
        //console.log('zelo')
        const action ={type:'addItem'}
        store.dispatch(action)
    }
  1. 有个上一节绑定遗留的问题,不知道咋解决;能用,但这个视频弃坑【毕竟2019年的,不跟着打了,看看思路】。。。看官方文档吧-见第四篇内容 image.png

第九节 用Redux实现ToDoList的删除功能

和第八节差不多,定义 action-deleteItem和对应reducer方法

if(action.type === 'deleteItem' ){ 
let newState = JSON.parse(JSON.stringify(state)) 
newState.list.splice(action.index,1) //删除数组中对应的值 
return newState }

第十节 代码抽常量ationType.js

第十一 方法抽入变量函数:actionCreators.js

  • 箭头表达式:()表参数,({})内是返回值
export const changeInputAction = (value)=>({ type:CHANGE_INPUT, value })
  • 代码引入后调用方式
changeInputValue(e){ const action = changeInputAction(e.target.value) store.dispatch(action) }

第十二节 注意别踩坑事项

  • store必须是唯一的,多个store是坚决不允许,应用只能有一个store空间
  • 只有store能改变自己的内容,Reducer不能改变
  • Reducer必须是纯函数:如果函数的调用参数相同,则永远返回相同的结果。它不依赖于程序执行期间函数外部任何状态或数据的变化,必须只依赖于其输入参数。Reducer里增加一个异步ajax函数,获取一些后端接口数据,然后再返回,这就是不允许的

第十三节 前后端分离

1、 把组件TodoList里的render()的内容抽进一个新组件TodoListUi

  1. 记得引相关库
  2. state咋办
  3. 调用的方法咋办
import React, { Component } from 'react';
class TodoListUi extends Component {

    render() { 
        return ( 
            <div style={{margin:'10px'}}>
                <div>
                    <Input 
                        placeholder={this.state.inputValue} 
                        style={{ width:'250px', marginRight:'10px'}}
                        onChange={this.changeInputValue}
                        value={this.state.inputValue}
                    />
                    <Button 
                        type="primary"
                        onClick={this.clickBtn}
                    >增加</Button>
                </div>
                <div style={{margin:'10px',width:'300px'}}>
                    <List
                        bordered
                        dataSource={this.state.list}
                        renderItem={(item,index)=>(<List.Item onClick={this.deleteItem.bind(this,index)}>{item}</List.Item>)}
                    />    
                </div>
            </div>
         );
    }
}

export default TodoListUi;

2、解决方法 都当属性传过来

render() { 
    return ( 
        <TodoListUI 
            inputValue={this.state.inputValue}
            list={this.state.list}
            changeInputValue={this.changeInputValue}
            clickBtn={this.clickBtn}
            deleteItem={this.deleteItem}
        />
    );
}

3、 UI组件调用方法更新,使用props

import React, { Component } from 'react';
import 'antd/dist/antd.css'
import { Input , Button , List } from 'antd'
class TodoListUi extends Component {

    render() { 
        return ( 
            <div style={{margin:'10px'}}>
                <div>
                    <Input  
                        style={{ width:'250px', marginRight:'10px'}}
                        onChange={this.props.changeInputValue}
                        value={this.props.inputValue}
                    />
                    <Button 
                        type="primary"
                        onClick={this.props.clickBtn}
                    >增加</Button>
                </div>
                <div style={{margin:'10px',width:'300px'}}>
                    <List
                        bordered
                        dataSource={this.props.list}
                        renderItem={(item,index)=>(<List.Item onClick={(index)=>{this.props.deleteItem(index)}}>{item}</List.Item>)}
                    />    
                </div>
            </div>
         );
    }
}

export default TodoListUi;

第十四节 纯UI组件修改为无状态对象函数,提升性能

  1. 纯UI不含构造方法,可以去掉Component
  2. 将其改为TodoListUI对象函数,返回JSX
  3. 因为是函数 可以改为传进来一个props参数,之后修改里边的所有props,去掉this

第十五节 Axios异步获取数据并和Redux结合

  1. 获取数据方法写在UI组件中,不影响reducer纯函数
componentDidMount(){
    axios.get('https://www.easy-mock.com/mock/5cfcce489dc7c36bd6da2c99/xiaojiejie/getList').then((res)=>{    
        const data = res.data
        const action = getListAction(data)
        store.dispatch(action)
    })
}
  1. dispatch后 回传store中修改后的state类型的对象
    if(action.type === GET_LIST ){ //根据type值,编写业务逻辑
        let newState = JSON.parse(JSON.stringify(state)) 
        newState.list = action.data.data.list //复制性的List数组进去
        return newState
    }

第十六节 redux-thunk中间件,官方也用了,store引入【Redux的中间件之一】

  1. 作用:在Dispatch一个Action之后,到达reducer之前,进行一些额外的操作,就需要用到middleware(中间件)。在实际工作中你可以使用中间件来进行日志记录、创建崩溃报告,调用异步接口或者路由;
  2. 使用增强函数解决插件的冲突:
import { createStore , applyMiddleware ,compose } from 'redux'  //  引入createStore方法
import reducer from './reducer'    
import thunk from 'redux-thunk'
//原window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__()
const composeEnhancers =   window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ ?
    window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__({}):compose

const enhancer = composeEnhancers(applyMiddleware(thunk))

const store = createStore( reducer, enhancer) // 创建数据存储仓库
export default store   //暴露出去

第十七节 Redux-thunk的使用方法

  1. 目的:这节课我们把向后台请求数据的程序放到中间件中,这样就形成了一套完整的Redux流程,所有逻辑都是在Redux的内部完成的
  2. 原本十二节里把Action组装抽记进actionCreators.js,UI组件里写的调用方法
  3. 优势:这个函数可以直接传递dispatch进去,这是自动的,然后我们直接用dispatch(action)传递就好了。现在我们就可以打开浏览器进行测试了。方法改写:componentDidMount
export const getTodoList = () =>{
    return (dispatch)=>{
        axios.get('https://www.easy-mock.com/mock/5cfcce489dc7c36bd6da2c99/xiaojiejie/getList').then((res)=>{
            const data = res.data
            const action = getListAction(data)
            dispatch(action)

        })
    }
}

第十八,十九节 Redux-saga【Redux的另一个中间件之一】

第二十节 React-Redux组件介绍与安装

功能:简化Redux流程,如果你公司不用这个插件,其实没必要耗费时间学。但是作为一篇文章,必须保证知识尽可能完整。(需要注意的是概念:React、Redux、React-redux是三个不同的东西)

  • 看了下,官方推荐demo和公司都用了

第二十一,二十二节 React-redux中的Provider和connect

一、Provider提供器:被provide包裹的都能获取到组件store,组件外编写

//index.js
import React from 'react';
import ReactDOM from 'react-dom';
import TodoList from './TodoList'
//---------关键代码--------start
import { Provider } from 'react-redux'
import store from './store'
//声明一个App组件,然后这个组件用Provider进行包裹。
const App = (
    <Provider store={store}>
        <TodoList />
    </Provider>
)
//---------关键代码--------end
ReactDOM.render(App, document.getElementById('root'));

二、connect连接器:组件内编写

  1. 改变组件的export方式 image.png

  2. 建立连接器的映射关系

image.png

三、React-redux的数据修改,UI组件响应事件排发

  1. 当前 export default connect(stateToProps,null)(TodoList); 二参为null,通过这个参数才能改变store中的值;
  2. 建立新的映射关系并取值

image.png

  1. 参考代码
import React, { Component } from 'react';
import store from './store'
import {connect} from 'react-redux'

class TodoList extends Component {
    constructor(props){
        super(props)
        this.state = store.getState()
    }
    render() { 
        return (
            <div>
                <div>
                    <input value={this.props.inputValue} onChange={this.props.inputChange} />
                    <button>提交</button>
                </div>
                <ul>
                    <li>JSPang</li>
                </ul>
            </div>
            );
    }
}
const stateToProps = (state)=>{
    return {
        inputValue : state.inputValue
    }
}

const dispatchToProps = (dispatch) =>{
    return {
        inputChange(e){
            console.log(e.target.value)
        }
    }
}

export default connect(stateToProps,dispatchToProps)(TodoList);
  • 到这一步只能实现组件内的UI效果,并没有派发出去修改store
  1. 派发action到store中,改写dispatchToProps
const dispatchToProps = (dispatch) =>{
    return {
        inputChange(e){
            let action = {
                type:'change_input',
                value:e.target.value
            }
            dispatch(action)
        }
    }
}
  1. 在reducer中编写处理方法
const defalutState = {
    inputValue : 'jspang',
    list :[]
}
export default (state = defalutState,action) =>{
    if(action.type === 'change_input'){
        let newState = JSON.parse(JSON.stringify(state))
        newState.inputValue = action.value
        return newState
    }
    return state
}

第二十四节代码优化

1、类似kotlin的精简思路,编写结构变量 把this.props抽出来

    render() { 
        let {inputValue ,inputChange,clickButton,list} = this.props;
        return (
            <div>
                <div>
                    <input value={inputValue} onChange={inputChange} />
                    <button onClick={clickButton}>提交</button>
                </div>
                <ul>
                    {
                        list.map((item,index)=>{
                            return (<li key={index}>{item}</li>)
                        })
                    }
                </ul>
            </div>
        );
    }
  1. 将UI组件修改为无状态UI对象,这套框架做到了UI对象内通过connect处理事件把dispatch派发出去了
  • 这里发现构造方法里的state没了???没了还能调?,像是有个默认值
    constructor(props){
        super(props)
        this.state = store.getState()
    }
import React from 'react';
import {connect} from 'react-redux'


const TodoList =(props)=>{
    let {inputValue ,inputChange,clickButton,list} = props; // 粘贴过来后,此处要进行修改
    return (
        <div>
            <div>
                <input value={inputValue} onChange={inputChange} />
                <button onClick={clickButton}>提交</button>
            </div>
            <ul>
                {
                    list.map((item,index)=>{
                        return (<li key={index}>{item}</li>)
                    })
                }
            </ul>
        </div>
    );
}



const stateToProps = (state)=>{
    return {
        inputValue : state.inputValue,
        list:state.list
    }
}

const dispatchToProps = (dispatch) =>{
    return {
        inputChange(e){
            let action = {
                type:'change_input',
                value:e.target.value
            }
            dispatch(action)
        },
        clickButton(){
            let action = {
                type:'add_item'
            }
            dispatch(action)
        }
    }
}
export default connect(stateToProps,dispatchToProps)(TodoList);