react进阶---React-Redux

351 阅读5分钟

先学习三个Hooks API

  1. useReducer
const [state, dispatch] = useReducer(reducer, initialArg, init)

useState的替代方案,它接收一个形如(state, action) => newState的reducer,并返回当前的state,以及与之匹配的dispatch方法

  1. useEffect

赋值给 useEffect 的函数会在组件渲染到屏幕之后延迟执行。默认情况下,effect 将在每轮渲染结束后执行,但你可以选择让它 在只有某些值改变的时候 才执行。

  1. useLayoutEffect

其函数签名与useEffect相同,但它会在所有的DOM变更之后同步调用 effect。可以使用它来读取DOM布局并同步触发重渲染。在浏览器执行绘制之前,内部的更新计划将被同步刷新。

import React, {useReducer, useLayoutEffect, useEffect} from "react";
import {counterReducer} from "../store";
const init = initArg => {
  //改变传入的初始值,这里是把string转化为int
  return initArg - 0;
};
export default function HooksPage(props) {
  const [state, dispatch] = useReducer(counterReducer, "0", init);
  useEffect(() => {
    console.log("useEffect"); //sy-log
});
  useLayoutEffect(() => {
    console.log("useLayoutEffect"); //sy-log
});
  console.log("---"); //sy-log
  return (
    <div>
      <h3>HooksPage</h3>
      <p>{state}</p>
      <button onClick={() => dispatch({type: "ADD"})}>add</button>
</div>
); }

使用react-redux

yarn add react-redux

提供了2个Api:(provider,connect)

connect

为组件提供数据和变更方法(连接React组件与Redux store。返回一个新的已与 Redux store 连接的组件类)

mapStateToProps(state,ownProps)

  1. 该回调函数必须返回一个纯对象,这个对象会和组件的props合并,如果定义该参数,组件将会监听 Redux store的变化,否则不监听。
  2. ownProps是当前组件的props,如果指定了,那么组件只要接收新的peops,mapStateToProps就会被调用,,mapStateToProps都会被重新计算,mapDispatchToProps也会被调用,所以为了避免一些性能问题,,我们通常不传ownProps参数。

mapDispatchToProps(dispatch,ownProps),

  1. 如果省略这个参数,默认情况下,dispatch会注入到props中。
  2. 如果传递的是一个对象,那么每个定义在该对象的函数都将被当作 Redux action creator,对象所 定义的方法名将作为属性名;每个方法将返回一个新的函数,函数中 方法会将action creator的返回值作为参数执行。这些属性会被合并到组件的 props 中。
  3. 如果传递的是一个函数,该函数将接收一个 函数,然后由你来决定如何返回一个对象。 4.ownProps是当前组件自身的props,如果指定了,那么只要组件接收到新的 props, mapDispatchToProps 就会被调用。注意性能!

mergeProps(stateProps, dispatchProps, ownProps)

  1. 如果指定了这个参数,mapStateToProps() 与 mapDispatchToProps() 的执行结果和组件自身 的 props 将传入到这个回调函数中。该回调函数返回的对象将作为 props 传递到被包装的组件 中。
connect([mapStateToProps],[mapDispatchToProps],[mergeProps],[options])

步骤一: provider为后代组件提供store,全局提供store---index.js

import React from "react";
import ReactDOM from "react-dom";
import "./index.css";
import App from "./App";
import {Provider} from "react-redux";
import store from "./store/";
// 把Provider放在根组件外层,使子组件能获得store 
ReactDOM.render(
  <Provider store={store}>
    <App />
  </Provider>,
  document.getElementById("root")
);

步骤二: 获取状态数据---ReactReduxPage.js

import React, { Component } from "react";
import { connect } from "react-redux";
class ReactReduxPage extends Component {
  render() {
    const { num, add, minus, asyAdd } = this.props;
    return (
      <div>
        <h1>ReactReduxPage</h1>
        <p>{num}</p>
        <button onClick={add}>add</button>
        <button onClick={minus}>minus</button>
</div>
); }
}
const mapStateToProps = state => {
  return {
    num: state,
  };
};
const mapDispatchToProps = {
  add: () => {
    return { type: "add" };
  },
  minus: () => {
    return { type: "minus" };
  }
};
export default connect(
mapStateToProps, //状态映射 mapStateToProps 
mapDispatchToProps, //派发事件映射
)(ReactReduxPage);

步骤三: 详情使用


// 注意 这里使用的是export default connect()()格式,通常在项目里会做拆分使用export default connect(mapStateToProps,mapDispatchToProps)(Component)

import React, {Component} from "react";
import {connect} from "react-redux";
import {bindActionCreators} from "redux";
// connect用于连接React组件与store, 返回一个新的已经与store连接的组件类(HOC) 
export default connect(
  // mapStateToProps Fucntion
  // !慎重定义ownProps,因为你一旦定义ownProps,那么每当ownProps发生改变的时候,当前的 mapStateToProps都会被调用,
  // !这里的state也会被重新计算,容易影响性能 state => {
  // console.log("mapStateToProps"); //sy-log
    return {
      count: state
      }; 
    },
  // mapDispatchToProps 接收Object||Fucntion
  // Object 此时props中没有dispacth,但是有action creators,内部实现dispatch // 
  // {
  //   add: () => ({type: "ADD"}),
  //   minus: () => ({type: "MINUS"})
  // }
  // Fucntion 参数是dispatch与ownProps
  // !慎重定义ownProps,因为你一旦定义ownProps,那么每当ownProps发生改变的时候,当前的
mapStateToProps都会被调用,容易影响性能 
  (dispatch, ownProps) => {
    console.log("mapDispatchToProps--", ownProps); //sy-log
    let creators = {
      add: payload => ({type: "ADD", payload}),
      minus: () => ({type: "MINUS"})
    };
    creators = bindActionCreators(creators, dispatch);
    return {dispatch, ...creators};
  }
)(
  class ReactReduxPage extends Component {
    add = () => {
      this.props.dispatch({type: "ADD"});
    };
  render() {
    console.log("props", this.props); //sy-log
    const {count, dispatch, add, minus} = this.props;
    return (
      <div>
        <h3>ReactReduxPage</h3>
        <p>omg:{count}</p>
        <button onClick={this.add}>add-use dispatch</button>
        <button onClick={() => add(100)}> add</button>
        <button onClick={minus}>minus</button>
      </div>
    ); 
  }
});

实现react-redux

高阶组件实现connect方法

import React, {useContext, useEffect, useReducer, useLayoutEffect} from "react";

// Provider在index.js,把store传递下来,用到了context
// 使得所有的子组件都有机会接收到store
const Context = React.createContext();
// 实现:connect(({count}) => ({count}), { add: ()=>({type: 'ADD})})(Cmp)
export const connect = (
  mapStateToProps = state => state,
  mapDispatchToProps
) => WrappedComponent => props => {
  // 读取store state
  const store = useContext(Context);
  const {getState, dispatch, subscribe} = store;
  const stateProps = mapStateToProps(getState());
  // dispatch object | function
  let dispatchProps = {dispatch};
  if (typeof mapDispatchToProps === "function") {
    dispatchProps = mapDispatchToProps(dispatch);
  } else if (typeof mapDispatchToProps === "object") {
    dispatchProps = bindActionCreators(mapDispatchToProps, dispatch);
  }

  // 这里实现了函数组件版本的forceUpdate,可去参考官网
  const [forceUpdate] = useReducer(x => x + 1, 0);

  // 这里用到了前文中说的useLayoutEffect,为了实时的更新订阅,而不是延迟,return实现订阅之后要取消订阅,[store]关联数据
  useLayoutEffect(() => {
    const unsubscribe = subscribe(() => {
      // store state 发生改变  forceUpdate是强制更新
      forceUpdate();
    });
    return () => {
      if (unsubscribe) {
        unsubscribe();
      }
    };
  }, [store]);

  return <WrappedComponent {...props} {...stateProps} {...dispatchProps} />;
};

// 提供者 提供store 因为store当中有state dispatch subscribe
export function Provider({store, children}) {
  return <Context.Provider value={store}>{children}</Context.Provider>;
}

function bindActionCreator(creator, dispatch) {
  return (...args) => dispatch(creator(...args));
}

export function bindActionCreators(creators, dispatch) {
  const obj = {};
  for (let key in creators) {
    obj[key] = bindActionCreator(creators[key], dispatch);
  }
  return obj;
}

react-redux hooks API及实现

  1. useSelector获取数据
  2. useDispatch获取dispatch
export function useSelector(selector) {
  const store = useStore();
  const {getState, subscribe} = store;
  const selectedState = selector(getState());

  // 手动实现一个forceUpdate
  const [forceUpdate] = useReducer(x => x + 1, 0);

  // 这里用到了前面解释hook时候的useLayoutEffect,为了实时,而不是延迟更新
  useLayoutEffect(() => {
    const unsubscribe = subscribe(() => {
      // store state 发生改变  forceUpdate是强制更新
      forceUpdate();
    });
    return () => {
      if (unsubscribe) {
        unsubscribe();
      }
    };
  }, [store]);

  return selectedState;
}

export function useDispatch() {
  const store = useStore();
  return store.dispatch;
}

function useStore() {
  const store = useContext(Context);
  return store;
}

react-router自定义hook使用

import React, {useCallback} from "react";
import {useSelector, useDispatch} from "../ReactRedux";

export default function ReduxHooksPage(props) {
  // 获取状态值
  const count = useSelector(({count}) => count);
  // 获取dispatch
  const dispatch = useDispatch();
  //  [] 作为 useCallback 的依赖列表。这确保了callback不会在再次渲染时改变,因此 React不会在非必要的时候调用它。
  // useMemo对参数缓存,useCallback对函数缓存,只有依赖项变更的时候执行或者改变
  // 用useCallback包裹以避免随渲染发生改变
  const add = useCallback(() => {
    dispatch({type: "ADD"});
  }, []);
  return (
    <div>
      <h3>ReduxHooksPage</h3>
      <p>{count}</p>
      <button onClick={add}>add</button>
    </div>
  );
}