React 4.0


# Redux

# 相关文档

  1. 英文文档: https://redux.js.org/
  2. 中文文档: http://www.redux.org.cn/
  3. Github: https://github.com/reactjs/redux

# Redux 是什么

  1. redux 是一个专门用于做 状态管理 的 JS 库 (不是 react 插件库)。
  2. 它可以用在 react, angular, vue 等项目中,但基本与 react 配合使用。
  3. 作用:集中式管理 react 应用中多个组件 共享 的状态。

# 什么时候用 Redux

  1. 某个组件的状态,需要让其他组件可以随时拿到(共享)。
  2. 一个组件需要改变另一个组件的状态(通信)。
  3. 总体原则:能不用就不用,如果不用比较吃力才考虑使用。

# Redux 工作流程

个人理解:Components 是客人 (点餐),Action 是服务员 (通过点餐宝把客人点的东西加工之后发送给老板),Store 是老板 (老板接收到之后给大厨),Reduce 是大厨 (大厨开始干活)

# Redux 的三个核心概念

# action

  1. 动作的对象
  2. 包含 2 个属性
    type:标识属性,值为字符串,唯一,必要属性
    data:数据属性,值类型任意,可选属性
  3. 例子: { type: 'ADD_STUDENT',data:{name: 'tom',age:18} }

# reducer

  1. 用于初始化状态、加工状态。
  2. 加工时,根据旧的 state 和 action, 产生新的 state 的纯函数。

# store

  1. 将 state、action、reducer 联系在一起的对象
  2. 如何得到此对象?
    1. import {createStore} from ‘redux’
    2. import reducer from ‘./reducers’
    3. const store = createStore(reducer)
  3. 此对象的功能?
    1. getState (): 得到 state
    2. dispatch (action): 分发 action, 触发 reducer 调用,产生新的 state
    3. subscribe (listener): 注册监听,当产生了新的 state 时,自动调用

# 求和案例

# redux 精简版

  • redux 文件夹 ->store.js

    // 该文件专门用于暴露一个store对象,整个应用只有一个store对象
    
    //引入createStore,专门用于创建redux中最为核心的store对象
    import { createStore } from "redux";
    //引入为Count组件服务的reducer
    import countReducer from "./count_reducer";
    //暴露store
    export default createStore(countReducer);
  • redux 文件夹 ->count_reducer.js

    /* 
    1.该文件是用于创建一个为Count组件服务的reducer,reducer的本质就是一个函数
    2.reducer函数会接到两个参数,分别为:之前的状态(preState),动作对象(action)
    */
    
    const initState = 0; //初始化状态
    export default function countReducer(preState = initState, action) {
      // preState = initState形参的默认值
      // 如果没传preState或者传递的preState是undefined,直接在这将preState的值改成0
      //从action对象中获取:type、data
      const { type, data } = action;
      //根据type决定如何加工数据
      switch (type) {
        case "increment": //如果是加
          return preState + data;
        case "decrement": //如果是减
          return preState - data;
        default:
          return preState;
      }
    }
  • components 文件夹 ->Count 组件

    import React, { Component } from "react";
    //引入store,用于获取redux中保存状态
    import store from "../../redux/store";
    
    export default class Count extends Component {
      state = { carName: "奔驰c63" };
    
      /* componentDidMount(){
          //用于订阅redux中的状态的
          //检测redux中状态的变化,只要变化,就调用render
          store.subscribe(()=>{
            this.setState({})
          })
        } */
    
      //加法
      increment = () => {
        const { value } = this.selectNumber;
        // 告诉redux应该做什么
        store.dispatch({ type: "increment", data: value * 1 });
      };
      //减法
      decrement = () => {
        const { value } = this.selectNumber;
        store.dispatch({ type: "decrement", data: value * 1 });
      };
      //奇数再加
      incrementIfOdd = () => {
        const { value } = this.selectNumber;
        // 专门获取状态的API
        const count = store.getState();
        if (count % 2 !== 0) {
          store.dispatch({ type: "increment", data: value * 1 });
        }
      };
      //异步加
      incrementAsync = () => {
        const { value } = this.selectNumber;
        setTimeout(() => {
          store.dispatch({ type: "increment", data: value * 1 });
        }, 500);
      };
    
      render() {
        return (
          <div>
            <h1>当前求和为:{store.getState()}</h1>
            <select ref={(c) => (this.selectNumber = c)}>
              <option value="1">1</option>
              <option value="2">2</option>
              <option value="3">3</option>
            </select>&nbsp;
            <button onClick={this.increment}>+</button>&nbsp;
            <button onClick={this.decrement}>-</button>&nbsp;
            <button onClick={this.incrementIfOdd}>当前求和为奇数再加</button>
            &nbsp;
            <button onClick={this.incrementAsync}>异步加</button>&nbsp;
          </div>
        );
      }
    }
  • src 文件夹 ->index.js

    import React from "react";
    import ReactDOM from "react-dom";
    import App from "./App";
    import store from "./redux/store";
    
    ReactDOM.render(<App />, document.getElementById("root"));
    
    // 把整个APP都重新调了下render,APP调了render,那么APP里边所有组件都需要重新调一下render
    // 检测redux中状态的变化,只要变化,就调用render
    store.subscribe(() => {
      ReactDOM.render(<App />, document.getElementById("root"));
    });
  • README.md

    (1).store.js:
          - 引入redux中的createStore函数,创建一个store
          - createStore调用时要传入一个为其服务的reducer
          3).记得暴露store对象
    
    (2).count_reducer.js:
          - reducer的本质是一个函数,接收:preState,action,返回加工后的状态
          - reducer有两个作用:初始化状态,加工状态
          - reducer被第一次调用时,是store自动触发的,
                  传递的preState是undefined,
                  传递的action是:{type:'xxx'}
    
    (3).在index.js中监测store中状态的改变,一旦发生改变重新渲染<App/>
        备注:redux只负责管理状态,至于状态的改变驱动着页面的展示,要靠我们自己写。

# redux 完整版

  • 新增的文件:redux 文件夹 ->constant.js

    /* 
    该模块是用于定义action对象中type类型的常量值,目的只有一个:便于管理的同时防止程序员单词写错
    */
    export const INCREMENT = "increment";
    export const DECREMENT = "decrement";
  • 新增的文件:redux 文件夹 ->count_action.js

    /* 
    该文件专门为Count组件生成action对象
    */
    import { INCREMENT, DECREMENT } from "./constant";
    
    export const createIncrementAction = (data) => ({ type: INCREMENT, data });
    export const createDecrementAction = (data) => ({ type: DECREMENT, data });
  • redux 文件夹 ->count_reducer.js

    import { INCREMENT, DECREMENT } from "./constant";
    
    const initState = 0; //初始化状态
    export default function countReducer(preState = initState, action) {
      const { type, data } = action;
      //根据type决定如何加工数据
      switch (type) {
        case INCREMENT: //如果是加
          return preState + data;
        case DECREMENT: //若果是减
          return preState - data;
        default:
          return preState;
      }
    }
  • components 文件夹 ->Count 组件

    //只修改了引入路径,其他地方没变,只需要将按钮点击时候的调用的函数名改一下即可
    //引入actionCreator,专门用于创建action对象
    import {
      createIncrementAction,
      createDecrementAction,
    } from "../../redux/count_action";
  • README.js

    1.count_action.js 专门用于创建action对象
    2.constant.js 放置容易写错的type值

# 异步 action 版

  • components 文件夹 ->Count 组件

    import React, { Component } from "react";
    //引入store,用于获取redux中保存状态
    import store from "../../redux/store";
    //引入actionCreator,专门用于创建action对象
    import {
      createIncrementAction,
      createDecrementAction,
      createIncrementAsyncAction,
    } from "../../redux/count_action";
    
    export default class Count extends Component {
      state = { carName: "奔驰c63" };
      //异步加
      incrementAsync = () => {
        const { value } = this.selectNumber;
        store.dispatch(createIncrementAsyncAction(value * 1, 500));
      };
    
      render() {
        return (
          <div>
            <h1>当前求和为:{store.getState()}</h1>
            <select ref={(c) => (this.selectNumber = c)}>
              <option value="1">1</option>
              <option value="2">2</option>
              <option value="3">3</option>
            </select>&nbsp;
            <button onClick={this.incrementAsync}>异步加</button>&nbsp;
          </div>
        );
      }
    }
  • redux 文件夹 ->count_action.js

    import { INCREMENT, DECREMENT } from "./constant";
    
    //同步action,就是指action的值为Object类型的一般对象
    export const createIncrementAction = (data) => ({ type: INCREMENT, data });
    export const createDecrementAction = (data) => ({ type: DECREMENT, data });
    
    //异步action,就是指action的值为函数,异步action中一般都会调用同步action,异步action不是必须要用的。
    export const createIncrementAsyncAction = (data, time) => {
      return (dispatch) => {
        setTimeout(() => {
          // 通知redux对data进行操作
          // 这里是调用同步action
          dispatch(createIncrementAction(data));
        }, time);
      };
    };
  • redux 文件夹 ->store.js

    /* 
    该文件专门用于暴露一个store对象,整个应用只有一个store对象
    */
    
    //引入createStore,专门用于创建redux中最为核心的store对象
    import { createStore, applyMiddleware } from "redux";
    //引入为Count组件服务的reducer
    import countReducer from "./count_reducer";
    //引入redux-thunk,用于支持异步action
    import thunk from "redux-thunk";
    //暴露store
    // applyMiddleware是用来执行中间件的
    export default createStore(countReducer, applyMiddleware(thunk));
  • README.js

    (1).明确:延迟的动作不想交给组件自身,想交给action
    (2).何时需要异步action:想要对状态进行操作,但是具体的数据靠异步任务返回。
    (3).具体编码:
        - npm install --save redux-thunk,并配置在store中
        - 创建action的函数不再返回一般对象,而是一个函数,该函数中写异步任务。
        - 异步任务有结果后,分发一个同步的action去真正操作数据。
    (4).备注:异步action不是必须要写的,完全可以自己等待异步任务的结果了再去分发同步action。

# React-Redux

npm i react-redux

# React-Redux 模型图

# React-Redux 的基本使用

  • App.jsx

    import React, { Component } from "react";
    import Count from "./containers/Count";
    import store from "./redux/store";
    
    export default class App extends Component {
      render() {
        return (
          <div>
            {/* 给容器组件传递store */}
            {/* store不能在容器组件中亲自引入,必须通过props传递 */}
            <Count store={store} />
          </div>
        );
      }
    }
  • index.js

    import React from "react";
    import ReactDOM from "react-dom";
    import App from "./App";
    import store from "./redux/store";
    
    ReactDOM.render(<App />, document.getElementById("root"));
    
    //监测redux中状态的改变,如redux的状态发生了改变,那么重新渲染App组件
    store.subscribe(() => {
      ReactDOM.render(<App />, document.getElementById("root"));
    });
  • containers 文件夹下 ->Count 文件夹 (容器组件)

    //引入Count的UI组件
    import CountUI from "../../components/Count";
    //引入action
    import {
      createIncrementAction,
      createDecrementAction,
      createIncrementAsyncAction,
    } from "../../redux/count_action";
    
    //从react-redux中引入connect用于连接UI组件与redux
    import { connect } from "react-redux";
    
    /* 
      1.mapStateToProps函数返回的是一个对象;
      2.返回的对象中的key就作为传递给UI组件props的key,value就作为传递给UI组件props的value
      3.mapStateToProps用于传递状态
    */
    function mapStateToProps(state) {
      return { count: state };
    }
    
    /* 
      1.mapDispatchToProps函数返回的是一个对象;
      2.返回的对象中的key就作为传递给UI组件props的key,value就作为传递给UI组件props的value
      3.mapDispatchToProps用于传递操作状态的方法
    */
    function mapDispatchToProps(dispatch) {
      return {
        //通知redux做对应的事情
        jia: (number) => dispatch(createIncrementAction(number)),
        jian: (number) => dispatch(createDecrementAction(number)),
        jiaAsync: (number, time) =>
          dispatch(createIncrementAsyncAction(number, time)),
      };
    }
    
    //使用connect()()创建并暴露一个Count的容器组件
    //connect的第一个参数里边有两个参数,这两个参数必须是函数
    //第一个参数是:redux中所保存的状态
    //第二个参数是:用于操作状态的方法
    //connect的第二个参数是建立连接的UI组件
    export default connect(mapStateToProps, mapDispatchToProps)(CountUI);
  • components 文件夹下 - Count 文件夹 (UI 组件)

    import React, { Component } from "react";
    
    export default class Count extends Component {
      state = { carName: "奔驰c63" };
    
      //加法
      increment = () => {
        const { value } = this.selectNumber;
        this.props.jia(value * 1);
      };
      //减法
      decrement = () => {
        const { value } = this.selectNumber;
        this.props.jian(value * 1);
      };
      //奇数再加
      incrementIfOdd = () => {
        const { value } = this.selectNumber;
        if (this.props.count % 2 !== 0) {
          this.props.jia(value * 1);
        }
      };
      //异步加
      incrementAsync = () => {
        const { value } = this.selectNumber;
        this.props.jiaAsync(value * 1, 500);
      };
    
      render() {
        return (
          <div>
            <h1>当前求和为:{this.props.count}</h1>
            <select ref={(c) => (this.selectNumber = c)}>
              <option value="1">1</option>
              <option value="2">2</option>
              <option value="3">3</option>
            </select>&nbsp;
            <button onClick={this.increment}>+</button>&nbsp;
            <button onClick={this.decrement}>-</button>&nbsp;
            <button onClick={this.incrementIfOdd}>当前求和为奇数再加</button>
            &nbsp;
            <button onClick={this.incrementAsync}>异步加</button>&nbsp;
          </div>
        );
      }
    }

# 总结

  • 明确两个概念:
    • UI 组件:不能使用任何 redux 的 api,只负责页面的呈现、交互等。
    • 容器组件:负责和 redux 通信,将结果交给 UI 组件。
  • 如何创建一个容器组件 ———— 靠 react-redux 的 connect 函数
    connect (mapStateToProps,mapDispatchToProps)(UI 组件)
    • mapStateToProps: 映射状态,返回值是一个对象
    • mapDispatchToProps: 映射操作状态的方法,返回值是一个对象
  • 备注 1:容器组件中的 store 是靠 props 传进去的,而不是在容器组件中直接引入
  • 备注 2:mapDispatchToProps,可以是一个函数,也可以是一个对象 (一般写成对象类型)

# 基本使用中的优化写法 1:简写 mapDispatch

  • containers 文件夹下 ->Count 文件夹 (容器组件)

    import React, { Component } from "react";
    //引入action
    import {
      createIncrementAction,
      createDecrementAction,
      createIncrementAsyncAction,
    } from "../../redux/count_action";
    //引入connect用于连接UI组件与redux
    import { connect } from "react-redux";
    
    //定义UI组件
    class Count extends Component {
      // render上边的内容和之前一样
      render() {
        return (
          <div>
            <h1>当前求和为:{this.props.count}</h1>
          </div>
        );
      }
    }
    
    //使用connect()()创建并暴露一个Count的容器组件
    export default connect(
      (state) => ({ count: state }),
    
      //mapDispatchToProps的一般写法
      /* dispatch => ({
        jia:number => dispatch(createIncrementAction(number)),
        jian:number => dispatch(createDecrementAction(number)),
        jiaAsync:(number,time) => dispatch(createIncrementAsyncAction(number,time)),
      }) */
    
      //mapDispatchToProps的简写
      //只需要将action提供过去,react-redux会自动做一个分发的动作
      {
        jia: createIncrementAction,
        jian: createDecrementAction,
        jiaAsync: createIncrementAsyncAction,
      }
    )(Count);

# 基本使用中的优化写法 2:Provider 组件的使用

容器组件默认拥有了监测 redux 里边状态改变的能力,不用在 index.js 中使用 store.subscribe 去监测了 (react-redux 只要你用了,就不用自己去监测 redux 状态的变化,因为容器组件已经有了监测的能力。react-redux 的核心就是 connect ()() 方法)

  • App.jsx

    import React, { Component } from "react";
    import Count from "./containers/Count";
    
    export default class App extends Component {
      render() {
        return (
          <div>
            <Count />
          </div>
        );
      }
    }
  • index.js

    import React from "react";
    import ReactDOM from "react-dom";
    import App from "./App";
    import store from "./redux/store";
    import { Provider } from "react-redux";
    
    ReactDOM.render(
      //把所有容器组件都需要的store交给Provider,然后Provider会把store精准的传递给需要store的容器组件
      //目的是让APP所有的后代容器组件都能接收到store
      <Provider store={store}>
        <App />
      </Provider>,
      document.getElementById("root")
    );

# 基本使用中的优化写法 3:整合 UI 组件和容器组件

  • containers 文件夹下 ->Count 文件夹

    import React, { Component } from "react";
    //引入action
    import {
      createIncrementAction,
      createDecrementAction,
      createIncrementAsyncAction,
    } from "../../redux/count_action";
    //引入connect用于连接UI组件与redux
    import { connect } from "react-redux";
    
    //定义UI组件
    class Count extends Component {
      // 此处要和之前的内容保持一致
      render() {
        return (
          <div>
            <h1>当前求和为:{this.props.count}</h1>
            <select ref={(c) => (this.selectNumber = c)}>
              <option value="1">1</option>
              <option value="2">2</option>
              <option value="3">3</option>
            </select>&nbsp;
            <button onClick={this.increment}>+</button>&nbsp;
            <button onClick={this.decrement}>-</button>&nbsp;
            <button onClick={this.incrementIfOdd}>当前求和为奇数再加</button>
            &nbsp;
            <button onClick={this.incrementAsync}>异步加</button>&nbsp;
          </div>
        );
      }
    }
    
    //使用connect()()创建并暴露一个Count的容器组件
    export default connect(
      (state) => ({ count: state }),
      //mapDispatchToProps的简写
      {
        jia: createIncrementAction,
        jian: createDecrementAction,
        jiaAsync: createIncrementAsyncAction,
      }
      // 此处将UI组件和容器组件写在了一起
    )(Count);

# 优化总结

  1. 容器组件和 UI 组件整合一个文件
  2. 无需自己给容器组件传递 store,给包裹一个即可。
  3. 使用了 react-redux 后也不用再自己检测 redux 中状态的改变了,容器组件可以自动完成这个工作。
  4. mapDispatchToProps 也可以简单的写成一个对象
  5. 一个组件要和 redux “打交道” 要经过哪几步?
    • 定义好 UI 组件 — 不暴露
    • 引入 connect 生成一个容器组件,并暴露,写法如下:
      connect(
      state => ({key:value}), //映射状态
      {key:xxxxxAction} //映射操作状态的方法
      )(UI 组件)
    • 在 UI 组件中通过 this.props.xxxxxxx 读取和操作状态

# 案例 2:数据共享版

  • redux 文件夹 ->store.js

    /* 
    该文件专门用于暴露一个store对象,整个应用只有一个store对象
    */
    
    //引入createStore,专门用于创建redux中最为核心的store对象
    import { createStore, applyMiddleware, combineReducers } from "redux";
    //引入为Count组件服务的reducer
    import countReducer from "./reducers/count";
    //引入为Count组件服务的reducer
    import personReducer from "./reducers/person";
    //引入redux-thunk,用于支持异步action
    import thunk from "redux-thunk";
    
    //combineReducers调用的时候传入的那个对象就是redux保存的总状态对象
    //combineReducers:汇总所有的reducer变为一个总的reducer
    const allReducer = combineReducers({
      he: countReducer,
      rens: personReducer,
    });
    
    //暴露store
    export default createStore(allReducer, applyMiddleware(thunk));
  • redux 文件夹 ->constant.js

    /* 
    该模块是用于定义action对象中type类型的常量值,目的只有一个:便于管理的同时防止程序员单词写错
    */
    export const INCREMENT = "increment";
    export const DECREMENT = "decrement";
    export const ADD_PERSON = "add_person";
  • Count.jsx

    // Count组件相比于之前的案例,本案例只在引入action这里做了修改,其余都一样
    import {
      createIncrementAction,
      createDecrementAction,
      createIncrementAsyncAction,
    } from "../../redux/actions/count";
    
    // 这里也做了修改
    export default connect(
      (state) => ({
        count: state.he,
        renshu: state.rens.length,
      }),
      {
        jia: createIncrementAction,
        jian: createDecrementAction,
        jiaAsync: createIncrementAsyncAction,
      }
    )(Count);
  • redux 文件夹 ->actios 文件夹 ->count.js

    // 本案例相比于之前的案例,只修改了constant的引入位置,其余的没有变化
    import { INCREMENT, DECREMENT } from "../constant";
  • redux 文件夹 ->reducers 文件夹 ->count.js

    // 本案例相比于之前的案例,只修改了constant的引入位置,其余的没有变化
    import { INCREMENT, DECREMENT } from "../constant";
  • redux 文件夹 ->actios 文件夹 ->person.js

    import { ADD_PERSON } from "../constant";
    
    //创建增加一个人的action动作对象
    export const createAddPersonAction = (personObj) => ({
      type: ADD_PERSON,
      data: personObj,
    });
  • redux 文件夹 ->reducers 文件夹 ->person.js

    import { ADD_PERSON } from "../constant";
    
    //初始化人的列表
    const initState = [{ id: "001", name: "tom", age: 18 }];
    
    export default function personReducer(preState = initState, action) {
      const { type, data } = action;
      switch (type) {
        case ADD_PERSON: //若是添加一个人
          return [data, ...preState];
        default:
          return preState;
      }
    }
  • Person.jsx

    import React, { Component } from "react";
    import { nanoid } from "nanoid";
    import { connect } from "react-redux";
    import { createAddPersonAction } from "../../redux/actions/person";
    
    class Person extends Component {
      addPerson = () => {
        const name = this.nameNode.value;
        const age = this.ageNode.value;
        const personObj = { id: nanoid(), name, age };
        this.props.jiaYiRen(personObj);
        this.nameNode.value = "";
        this.ageNode.value = "";
      };
    
      render() {
        return (
          <div>
            <h2>我是Person组件,上方组件求和为{this.props.he}</h2>
            <input
              ref={(c) => (this.nameNode = c)}
              type="text"
              placeholder="输入名字"
            />
            <input
              ref={(c) => (this.ageNode = c)}
              type="text"
              placeholder="输入年龄"
            />
            <button onClick={this.addPerson}>添加</button>
            <ul>
              {this.props.yiduiren.map((p) => {
                return (
                  <li key={p.id}>
                    {p.name}--{p.age}
                  </li>
                );
              })}
            </ul>
          </div>
        );
      }
    }
    
    export default connect(
      (state) => ({ yiduiren: state.rens, he: state.he }), //映射状态
      { jiaYiRen: createAddPersonAction } //映射操作状态的方法
    )(Person);

# 总结

  • 定义一个 Pserson 组件,和 Count 组件通过 redux 共享数据。
  • 为 Person 组件编写:reducer、action,配置 constant 常量。
  • 重点:Person 的 reducer 和 Count 的 Reducer 要使用 combineReducers 进行合并,合并后的总状态是一个对象!!!
  • 交给 store 的是总 reducer,最后注意在组件中取出状态的时候,记得 “取到位”。

# 纯函数和高阶函数

# 纯函数

  • 一类特别的函数:只要是同样的输入 (实参),必定得到同样的输出 (返回),比如:demo (1) =>1,demo (20)=>20, 但如果 demo (12)=>99 这个就不是,因为入参和输出的数字不一样

  • 必须遵守以下一些约束

    • 不得改写参数数据
    • 不会产生任何副作用,例如网络请求,输入和输出设备
    • 不能调用 Date.now () 或者 Math.random () 等不纯的方法
  • redux 的 reducer 函数必须是一个纯函数

# 高阶函数

  • 理解:一类特别的函数

    • 情况 1: 参数是函数
    • 情况 2: 返回是函数
  • 常见的高阶函数:

    • 定时器设置函数
    • 数组的 forEach ()/map ()/filter ()/reduce ()/find ()/bind ()
    • promise
    • react-redux 中的 connect 函数
  • 作用:能实现更加动态,更加可扩展的功能

# redux 开发者工具

  • 安装 chrome 浏览器插件:
    React Devtools

  • 下载工具依赖包:
    npm install --save-dev redux-devtools-extension

  • store.js

    import { createStore, applyMiddleware, combineReducers } from "redux";
    import countReducer from "./reducers/count";
    import personReducer from "./reducers/person";
    import thunk from "redux-thunk";
    //引入redux-devtools-extension
    import { composeWithDevTools } from "redux-devtools-extension";
    
    //汇总所有的reducer变为一个总的reducer
    const allReducer = combineReducers({
      he: countReducer,
      rens: personReducer,
    });
    
    //暴露store
    export default createStore(
      allReducer,
      composeWithDevTools(applyMiddleware(thunk))
    );

# 总结

  1. yarn add redux-devtools-extension
  2. store 中进行配置
    import {composeWithDevTools} from ‘redux-devtools-extension’
    const store = createStore(allReducer,composeWithDevTools(applyMiddleware(thunk)))

# React 扩展

# 1. setState

# setState 更新状态的 2 种写法

  • 对象式的 setState:setState (stateChange, [callback])

    1.stateChange为状态改变对象(该对象可以体现出状态的更改)
    2.callback是可选的回调函数, 它在状态更新完毕、界面也更新后(render调用后)才被调用
    
    state = {count:0}
    add = ()=>{
      const {count} = this.state
      this.setState({count:count+1})
      console.log(this.state.count) //结果是0(更新之前的状态),React状态更新是异步的。应该是输出完这句话之后,React再在页面上展示1(更新之后的状态)
    
      //如果是下面的写法,会输出1
      this.setState({count:count+1},()=>{
        console.log(this.state.count) //输出1
      })
    }
  • 函数式的 setState:setState (updater, [callback])

    1.updater为返回stateChange对象的函数。
    2.updater可以接收到state和props。
    4.callback是可选的回调函数, 它在状态更新、界面也更新后(render调用后)才被调用。
    
    state = {count:0}
    add = ()=>{
      //这里会接收到state和props的值,所以这里不用提前取state的值
      this.setState((state,props)=>{
        return {count:state.count+1}
      })
    }
  • 总结

    1.对象式的setState是函数式的setState的简写方式(语法糖)
    2.使用原则:
        (1).如果新状态不依赖于原状态 ===> 使用对象方式
        (2).如果新状态依赖于原状态 ===> 使用函数方式
        (3).如果需要在setState()执行后获取最新的状态数据,
          要在第二个callback函数中读取
        (4).开发当中怎么舒服怎么来,不用纠结于1,2条

# 2. lazyLoad

# 路由组件的 lazyLoad

//1.通过React的lazy函数配合import()函数动态加载路由组件 ===> 路由组件代码会被分开打包
const Login = lazy(()=>import('@/pages/Login'))

//2.通过<Suspense>指定在加载得到路由打包文件前显示一个自定义loading界面
<Suspense fallback={<h1>loading.....</h1>}>
  <Switch>
    <Route path="/xxx" component={Xxxx}/>
    <Redirect to="/login"/>
  </Switch>
</Suspense>

# 3. Hooks

# 1. React Hook/Hooks 是什么?

  • Hook 是 React 16.8.0 版本增加的新特性 / 新语法
  • 可以让你在函数组件中使用 state 以及其他的 React 特性

# 2. 三个常用的 Hook

  • State Hook: React.useState()
  • Effect Hook: React.useEffect()
  • Ref Hook: React.useRef()

# 3. State Hook

  • State Hook 让函数组件也可以有 state 状态,并进行状态数据的读写操作

  • 语法: const [xxx, setXxx] = React.useState (initValue)

  • useState () 说明:
    参数:第一次初始化指定的值在内部作缓存
    返回值:包含 2 个元素的数组,第 1 个为内部当前状态值,第 2 个为更新状态值的函数

  • setXxx () 2 种写法:
    setXxx (newValue): 参数为非函数值,直接指定新的状态值,内部用其覆盖原来的状态值
    setXxx (value => newValue): 参数为函数,接收原本的状态值,返回新的状态值,内部用其覆盖原来的状态值

  • 案例

    import React, { useState } from "react";
    export default function Example() {
      const [count, setCount] = useState(0);
      return (
        <div>
          <h1>you click {count} times</h1>
          // 这里是点击一次触发一次,如果不是箭头函数的话有可能会上来就执行
          <button
            onClick={() => {
              setCount(count + 1);
            }}
          >
            click me
          </button>
        </div>
      );
    }

# 4. Effect Hook

  • Effect Hook 可以让你在函数组件中执行副作用操作 (用于模拟类组件中的生命周期钩子)

  • React 中的副作用操作:

    • 发 ajax 请求数据获取
    • 设置订阅 / 启动定时器
    • 手动更改真实 DOM
  • 语法和说明:

    useEffect(() => {
      // 在此可以执行任何带副作用操作
      return () => {
        // 在组件卸载前执行
        // 在此做一些收尾工作, 比如清除定时器/取消订阅等
      };
    }, [stateValue]); // 如果指定的是[]代表谁也不监测, 回调函数只会在第一次render()后执行
    // stateValue代表要监测的state(可以监测多个state),只有当state更新的时候才会执行函数体
    // 如果没写第二个参数,代表谁也监测
  • 可以把 useEffect Hook 看做如下三个函数的组合

    • componentDidMount()
    • componentDidUpdate()
    • componentWillUnmount()

# 5. Ref Hook

  • Ref Hook 可以在函数组件中存储 / 查找组件内的标签或任意其它数据
  • 语法: const refContainer = useRef ()
  • 作用:保存标签对象,功能与 React.createRef () 一样
# 案例
import React from "react";
import ReactDOM from "react-dom";

function Demo() {
  const [count, setCount] = React.useState(0);
  const myRef = React.useRef();

  React.useEffect(() => {
    let timer = setInterval(() => {
      setCount((count) => count + 1);
    }, 1000);
    // 这里返回的函数相当于componentWillUnmount生命周期钩子
    return () => {
      clearInterval(timer);
    };
  }, []);

  //加的回调
  function add() {
    //setCount(count+1) //第一种写法
    setCount((count) => count + 1);
  }

  //提示输入的回调
  function show() {
    alert(myRef.current.value);
  }

  //卸载组件的回调
  function unmount() {
    ReactDOM.unmountComponentAtNode(document.getElementById("root"));
  }

  return (
    <div>
      <input type="text" ref={myRef} />
      <h2>当前求和为:{count}</h2>
      <button onClick={add}>点我+1</button>
      <button onClick={unmount}>卸载组件</button>
      <button onClick={show}>点我提示数据</button>
    </div>
  );
}

export default Demo;

# 6. Context Hook

  • 让父子组件传值更简单 (解决父子组件传值的问题)
    在用类声明组件时,父子组件的传值是通过组件属性和 props 进行的,那现在使用方法 (Function) 来声明组件,已经没有了 constructor 构造函数也就没有了 props 的接收,那父子组件的传值就成了一个问题。React Hooks 为我们准备了 useContext。它可以帮助我们跨越组件层级直接传递变量,实现共享。需要注意的是 useContext 和 redux 的作用是不同的,一个解决的是组件之间值传递的问题,一个是应用中统一管理状态的问题,但通过和 useReducer 的配合使用,可以实现类似 Redux 的作用。

  • 案例

    import React, { useState, createContext, useContext } from "react";
    // createContext 创建 context
    // useContext 接收上下文变量
    //===关键代码
    const CountContext = createContext();
    
    function Counter() {
      const count = useContext(CountContext); //一句话就可以得到count
      return <h2>{count}</h2>;
    }
    
    function Example() {
      const [count, setCount] = useState(0);
    
      return (
        <div>
          <p>You clicked {count} times</p>
          <button
            onClick={() => {
              setCount(count + 1);
            }}
          >
            click me
          </button>
          {/*======关键代码 */}
          <CountContext.Provider value={count}>
            <Counter />
          </CountContext.Provider>
        </div>
      );
    }
    export default Example;

# 7.useReducer 和 useContext 联合使用实现 redux

# 8. useMemo

useMemo 主要用来解决使用 React hooks 产生的无用渲染的性能问题。使用 function 的形式来声明组件,失去了 shouldCompnentUpdate(在组件更新之前)这个生命周期,也就是说我们没有办法通过组件更新前条件来决定组件是否更新。而且在函数组件中,也不再区分 mount 和 update 两个状态,这意味着函数组件的每一次调用都会执行内部的所有逻辑,就带来了非常大的性能损耗。useMemo 和 useCallback 都是解决上述性能问题的。

useMemo 缓存的是结果 (state),useCallback 缓存的是函数

# 案例
import React, { useState, useMemo } from "react";

function Example() {
  const [xiaohong, setXiaohong] = useState("小红待客状态");
  const [zhiling, setZhiling] = useState("志玲待客状态");
  return (
    <>
      <button
        onClick={() => {
          setXiaohong(new Date().getTime());
        }}
      >
        小红
      </button>
      <button
        onClick={() => {
          setZhiling(new Date().getTime() + ",志玲向我们走来了");
        }}
      >
        志玲
      </button>
      <ChildComponent name={xiaohong}>{zhiling}</ChildComponent>
    </>
  );
}

function ChildComponent({ name, children }) {
  function changeXiaohong(name) {
    console.log("她来了,她来了。小红向我们走来了");
    return name + ",小红向我们走来了";
  }

  const actionXiaohong = useMemo(() => changeXiaohong(name), [name]);
  return (
    <>
      <div>{actionXiaohong}</div>
      <div>{children}</div>
    </>
  );
}

export default Example;

# 9. 自定义 Hooks

其实自定义 Hooks 函数和用 Hooks 创建组件很相似,跟我们平时用 JavaScript 写函数几乎一模一样,可能就是多了些 React Hooks 的特性,自定义 Hooks 函数偏向于功能,而组件偏向于界面和业务逻辑。由于差别不大,所以使用起来也是很随意的。如果是小型项目是可以的,但是如果项目足够复杂,这会让项目结构不够清晰。所以学习自定义 Hooks 函数还是很有必要的。

# 案例
function useWinSize() {
  const [size, setSize] = useState({
    width: document.documentElement.clientWidth,
    height: document.documentElement.clientHeight,
  });

  const onResize = useCallback(() => {
    setSize({
      width: document.documentElement.clientWidth,
      height: document.documentElement.clientHeight,
    });
  }, []);
  useEffect(() => {
    window.addEventListener("resize", onResize);
    return () => {
      window.removeEventListener("resize", onResize);
    };
  }, []);

  return size;
}

function Example() {
  const size = useWinSize();
  return (
    <div>
      页面Size:{size.width}x{size.height}
    </div>
  );
}

export default Example;

# 4. Fragment

# 使用

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

export default class Demo extends Component {
	render() {
		return (
			<Fragment key={1}>
				<input type="text"/>
				<input type="text"/>
			</Fragment>
		)
	}
}

<Fragment><Fragment>或者 <></>(空标签),但是Fragment允许在遍历的时候写key值,但是空标签不允许写任何值

# 作用

可以不用必须有一个真实的 DOM 根标签了

# 5. Context

# 理解

一种组件间通信方式,常用于【祖组件】与【后代组件】间通信,也就是爷孙辈以及爷孙之后的组件之间的通信

# 使用

1) 创建Context容器对象:
	const XxxContext = React.createContext()

2) 渲染子组时,外面包裹xxxContext.Provider, 通过value属性给后代组件传递数据:
	<xxxContext.Provider value={数据}>
		子组件
  </xxxContext.Provider>

3) 后代组件读取数据:

	//第一种方式:仅适用于类组件
	  static contextType = xxxContext  // 声明接收context
	  this.context // 读取context中的value数据

	//第二种方式: 函数组件与类组件都可以
	  <xxxContext.Consumer>
	    {
	      value => ( // value就是context中的value数据
	        要显示的内容
	      )
	    }
	  </xxxContext.Consumer>

# 案例

import React, { Component } from "react";
import "./index.css";

//创建Context对象
const MyContext = React.createContext();
const { Provider, Consumer } = MyContext;
export default class A extends Component {
  state = { username: "tom", age: 18 };

  render() {
    const { username, age } = this.state;
    return (
      <div className="parent">
        <h3>我是A组件</h3>
        <h4>我的用户名是:{username}</h4>
        <Provider value={{ username, age }}>
          <B />
        </Provider>
      </div>
    );
  }
}

class B extends Component {
  render() {
    return (
      <div className="child">
        <h3>我是B组件</h3>
        <C />
      </div>
    );
  }
}

/* class C extends Component {
	//声明接收context
	static contextType = MyContext
	render() {
		const {username,age} = this.context
		return (
			<div className="grand">
				<h3>我是C组件</h3>
				<h4>我从A组件接收到的用户名:{username},年龄是{age}</h4>
			</div>
		)
	}
} */

function C() {
  return (
    <div className="grand">
      <h3>我是C组件</h3>
      <h4>
        我从A组件接收到的用户名:
        <Consumer>{(value) => `${value.username},年龄是${value.age}`}</Consumer>
      </h4>
    </div>
  );
}

# 注意

在应用开发中一般不用context, 一般都用它的封装react插件

# 6. 组件优化

# Component 的 2 个问题

  1. 只要执行 setState (), 即使不改变状态数据,组件也会重新 render () ==> 效率低

  2. 只当前组件重新 render (), 就会自动重新 render 子组件,纵使子组件没有用到父组件的任何数据 ==> 效率低

# 效率高的做法

只有当组件的 state 或 props 数据发生改变时才重新 render ()

# 原因

Component 中的 shouldComponentUpdate () 总是返回 true

# 解决

  • 办法 1:

    • 重写 shouldComponentUpdate () 方法
    • 比较新旧 state 或 props 数据,如果有变化才返回 true, 如果没有返回 false
  • 办法 2:

    • 使用 PureComponent:PureComponent 重写了 shouldComponentUpdate (), 只有 state 或 props 数据有变化才返回 true

    • 注意:
      只是进行 state 和 props 数据的浅比较,如果只是数据对象内部的数据变了,返回 false (如果还和之前的对象一样,返回 false)。不要直接修改 state 数据,而是要产生新数据,这样才可以触发

      state = { car: "奔驰", stus: ["a", "b"] };
      
      // 这是对的
      this.setState({ car: "奥迪" });
      const { stus } = this.state;
      this.setState({ stus: ["c", ...stus] }); //产生了新数据
      
      // 这是错误的
      const obj = this.state;
      obj.car = "奥迪";
      console.log(obj === this.state); // true
      this.setState(obj);

      项目中一般使用 PureComponent 来优化


# 7. render props

# 如何向组件内部动态传入带内容的结构 (标签)?

  • Vue 中:
    使用 slot 技术,也就是通过组件标签体传入结构
  • React 中:
    使用 children props: 通过组件标签体传入结构
    使用 render props: 通过组件标签属性传入结构,而且可以携带数据,一般用 render 函数属性

# children props

<A>
  <B>xxxx</B>
</A>;
{
  this.props.children;
}

问题:如果 B 组件需要 A 组件内的数据,==> 做不到

# render props

<A render={(data) => <C data={data}></C>}></A>
// 这里的data是A组件中传递的state

A 组件: {this.props.render (内部 state 数据)}
C 组件:读取 A 组件传入的数据显示

# 8. 错误边界

# 理解:

错误边界 (Error boundary):用来捕获后代组件错误,渲染出备用页面

# 特点:

只能捕获后代组件生命周期产生的错误,不能捕获自己组件产生的错误和其他组件在合成事件、定时器中产生的错误

# 使用方式:

getDerivedStateFromError 配合 componentDidCatch

// 生命周期函数,一旦后台组件报错,就会触发
static getDerivedStateFromError(error) {
    console.log(error);
    // 在render之前触发
    // 返回新的state
    return {
        hasError: true,
    };
}

componentDidCatch(error, info) {
    // 统计页面的错误。发送请求发送到后台去
    console.log(error, info);
}

# 9. 组件通信方式总结

# 组件间的关系:

  • 父子组件
  • 兄弟组件(非嵌套组件)
  • 祖孙组件(跨级组件)

# 几种通信方式:

  • props:
    • children props
    • render props
  • 消息订阅 - 发布:
    pubs-sub、event 等等
  • 集中式管理:
    redux、dva 等等
  • conText:
    生产者 - 消费者模式

# 比较好的搭配方式:

  • 父子组件:props
  • 兄弟组件:消息订阅 - 发布、集中式管理
  • 祖孙组件 (跨级组件):消息订阅 - 发布、集中式管理、conText (开发用的少,封装插件用的多)

文章作者: Mrr-cxh
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 Mrr-cxh !
赏
 上一篇
ReactRouter6 ReactRouter6
React 是一个声明式,高效且灵活的用于构建用户界面的 JavaScript 库。使用 React 可以将一些简短、独立的代码片段组合成复杂的 UI 界面,这些代码片段被称作“组件”。
2022-06-16
下一篇 
React 3.0 React 3.0
React 是一个声明式,高效且灵活的用于构建用户界面的 JavaScript 库。使用 React 可以将一些简短、独立的代码片段组合成复杂的 UI 界面,这些代码片段被称作“组件”。
2022-05-12
  目录