Redux

为什么使用 redux => 单向数据流、缺少共享数据,需要状态提升,为了解决组件的数据共享问题
状态、读取改变、订阅

1、 手写 redux

1-1、步骤

  • 创建 createStore 库,内部维护 state(通过闭包保护)
  • 通过 getState 读取 state
  • 通过 dispatch 创建唯一合法修改 state 的途径,接受一个动作 action,调用 reducer 返回新的 state,修改 state
  • dispatch 过来的动作交给 reducer 去处理, reducer 接受一个老得 state,action,返回一个新的 state
  • subscribe: 订阅通过,发布订阅模式,处理每次 dispatch 后的渲染, 每次订阅后返回一个取消订阅的方法

1-2、 redux/createStore.js

  1. // 创建仓库,保护state reducer 是保安 是处理器 由外部传入
  2. function createStore(reducer) {
  3. let state;
  4. let listeners = [];
  5. function getState() {
  6. console.log(state, "??");
  7. return state;
  8. }
  9. // 想要修改状态,只能通过派发动作的方式 通过传入reducer 将动作写活 传入dispatch中调用 返回新的状态,进而修改仓库中的状态
  10. function dispatch(action) {
  11. state = reducer(state, action);
  12. listeners.forEach((l) => l());
  13. }
  14. // 默认初始执行一下 更新一下状态的初始值
  15. dispatch({ type: "@@TYPE/REDUX_INIT" });
  16. // 发布订阅 用来自动渲染 不是必要的
  17. function subscribe(fn) {
  18. listeners.push(fn);
  19. // 取消订阅 返回一个函数让用户自己取消订阅
  20. return function () {
  21. listeners = listeners.filter((item) => item !== fn);
  22. };
  23. }
  24. return { getState, dispatch, subscribe };
  25. }
  26. export default createStore;

1-3、 在 html 中使用

index.html

  1. <!DOCTYPE html>
  2. <html lang="en">
  3. <head>
  4. <meta charset="utf-8" />
  5. </head>
  6. <body>
  7. <noscript>You need to enable JavaScript to run this app.</noscript>
  8. <div id="title"></div>
  9. <div id="content"></div>
  10. </body>
  11. </html>

index.js

  1. import { createStore } from "./redux/index.js";
  2. // 渲染 使用
  3. function renderApp(state) {
  4. let title = document.querySelector("#title");
  5. let content = document.querySelector("#content");
  6. title.innerHTML = state.title.text;
  7. title.style.color = state.title.color;
  8. content.innerHTML = state.content.text;
  9. content.style.color = state.content.color;
  10. }
  11. // 常量 用的地方多 避免出错
  12. const CHCNGETITLECOLOR = "CHCNGETITLECOLOR";
  13. const CHCNGECONTENTCOLOR = "CHCNGECONTENTCOLOR";
  14. let initState = {
  15. title: {
  16. color: "red",
  17. text: "标题",
  18. },
  19. content: {
  20. color: "green",
  21. text: "内容",
  22. },
  23. };
  24. // 管理员 接收老状态、动作 返回一个新状态 reducer 中的处理 每次都返回新对象 性能优化考虑
  25. function reducer(state = initState, action) {
  26. switch (action.type) {
  27. case "CHCNGETITLECOLOR":
  28. // state.title.color = action.color;
  29. return { ...state, title: { ...state.title, color: action.color } };
  30. case "CHCNGECONTENTCOLOR":
  31. return { ...state, content: { ...state.content, color: action.color } };
  32. default:
  33. return state;
  34. }
  35. }
  36. let store = createStore(reducer);
  37. function render() {
  38. renderApp(store.getState());
  39. }
  40. render();
  41. let unsubscribe = store.subscribe(render);
  42. console.log(unsubscribe, "unsubscribe");
  43. setTimeout(() => {
  44. // 派发动作 修改状态
  45. store.dispatch({ type: CHCNGETITLECOLOR, color: "yellow" });
  46. unsubscribe();
  47. store.dispatch({ type: CHCNGECONTENTCOLOR, color: "orange" });
  48. }, 1000);

1-4、 在 react 中使用

index.js

  1. import React from "react";
  2. import ReactDOM from "react-dom";
  3. import { createStore } from "./redux/index.js";
  4. function reducer(state = { number: 0 }, action) {
  5. switch (action.type) {
  6. case "ADD":
  7. return { ...state, number: state.number + 1 };
  8. case "MINUS":
  9. return { ...state, number: state.number - 1 };
  10. default:
  11. return state;
  12. }
  13. }
  14. const store = createStore(reducer);
  15. class App extends React.Component {
  16. constructor(props) {
  17. super(props);
  18. this.state = {
  19. number: store.getState().number,
  20. };
  21. }
  22. componentDidMount() {
  23. this.unsubscribe = store.subscribe(() =>
  24. this.setState({ number: store.getState().number })
  25. );
  26. }
  27. componentWillUnmount() {
  28. this.unsubscribe();
  29. }
  30. render() {
  31. return (
  32. <div>
  33. <div>{this.state.number}</div>
  34. <button onClick={() => store.dispatch({ type: "ADD" })}>+</button> <br />
  35. <button onClick={() => store.dispatch({ type: "MINUS" })}>-</button>
  36. </div>
  37. );
  38. }
  39. }
  40. class Hello extends React.Component {
  41. render() {
  42. return <div>hello</div>;
  43. }
  44. }
  45. ReactDOM.render(
  46. <>
  47. <App></App>
  48. <Hello></Hello>
  49. </>,
  50. document.getElementById("root")
  51. );

1-5、 bindActionCreators.js

原理: 抽象函数简化 store 中状态的绑定, 将 store.dispatch({…}) 抽到函数中去完成

  1. function bindActionCreators(actionCreators, dispatch) {
  2. if (typeof actionCreators === "function") {
  3. return (...args) => dispatch(actionCreators(...args));
  4. }
  5. let bondActionCreators = {};
  6. for (const key in actionCreators) {
  7. bondActionCreators[key] = (...args) =>
  8. dispatch(actionCreators[key](...args));
  9. }
  10. return bondActionCreators;
  11. }

index.js

  1. import React from "react";
  2. import ReactDOM from "react-dom";
  3. import { createStore, bindActionCreators } from "./redux/index.js";
  4. // 1、传入函数
  5. // let add = () => ({ type: "ADD" })
  6. // let minus = () => ({ type: "MINUS" })
  7. // add = combineReducers(store.dispatch, add)
  8. // minus = combineReducers(store.dispatch, minus)
  9. // 2、 传入对象
  10. let actions = {
  11. add: (args) => ({ type: "ADD", payload: args }),
  12. minus: (args) => ({ type: "MINUS", payload: args }),
  13. };
  14. actions = bindActionCreators(actions, store.dispatch);
  15. const store = createStore(reducer);
  16. function reducer(state = { number: 0 }, actions) {
  17. switch (actions.type) {
  18. case "ADD":
  19. return { ...state, number: state.number + actions.payload };
  20. case "MINUS":
  21. return { ...state, number: state.number - actions.payload };
  22. default:
  23. return state;
  24. }
  25. }
  26. class App extends React.Component {
  27. constructor(props) {
  28. super(props);
  29. this.state = {
  30. number: store.getState().number,
  31. };
  32. }
  33. componentDidMount() {
  34. this.unsubscribe = store.subscribe(() =>
  35. this.setState({ number: store.getState().number })
  36. );
  37. }
  38. componentWillUnmount() {
  39. this.unsubscribe();
  40. }
  41. render() {
  42. return (
  43. <div>
  44. <div>{this.state.number}</div>
  45. <button onClick={() => actions.add(5)}>+</button>
  46. <br />
  47. <button onClick={() => actions.minus(5)}>-</button>
  48. </div>
  49. );
  50. }
  51. }
  52. ReactDOM.render(<App></App>, document.getElementById("root"));

1-6、 combineReducers.js

redux 永远只有一个仓库,reducer 也只有一个, 状态树也只能有一个, 但是组件可能有 N 个多,reducer 会非常复杂,需要分工
解决 通过 combineReducers 将多个 reducer 合在一起

redux/combineReducers.js

  1. function combineReducers(reducers = {}) {
  2. let reducerKeys = Object.keys(reducers); // [counter1,counter2]
  3. return function (state = {}, action) {
  4. let hasChange = false; // 状态是否修改
  5. let nextState = {};
  6. for (let i = 0; i < reducerKeys.length; i++) {
  7. let key = reducerKeys[i]; // counter1
  8. const previousStateForKey = state[key]; // {number:0}
  9. const reducer = reducers[key]; // counter1
  10. let nextStateForKey = reducer(previousStateForKey, action); // {type:'ADD1'} {number:0}
  11. nextState[key] = nextStateForKey;
  12. hasChange = hasChange || nextStateForKey !== previousStateForKey;
  13. }
  14. return hasChange ? nextState : state;
  15. };
  16. }
  17. export default combineReducers;

reducers/index.js

  1. import counter1 from './counter1'
  2. import counter2 from './counter2'
  3. import { combineReducers } from '../../redux/index'
  4. /**
  5. * {
  6. * counter1: { number1:0},
  7. * counter1: { number2:0}
  8. * }
  9. */
  10. // export default function(state={},action){
  11. // let nextState = {};
  12. // nextState.counter1 = counter1(state.counter1,action)
  13. // nextState.counter2 = counter2(state.counter2,action)
  14. // console.log(nextState,'nextState...object');
  15. // return nextState;
  16. // }
  17. let reducers = combineReducers({
  18. counter1,
  19. counter2
  20. })
  21. export default reducers

1-7 Context 上下文

1-7-1 使用

使用 方法一: 通过static 静态方式获取 static contextType = ThemeContext;
通过 this.context 获取

  1. let ThemeContext = React.createContext(); // Provider Consumer
  2. class Content extends React.Component {
  3. static contextType = ThemeContext;
  4. render() {
  5. return (
  6. <div style={{ border: `5px solid ${this.context.color}` }}>Content
  7. <button onClick={()=> this.context.changeColor('#f00')}>变红</button>
  8. <button onClick={()=> this.context.changeColor('#0f0')}>变绿</button>
  9. </div>
  10. );
  11. }
  12. }
  13. class Page extends React.Component {
  14. constructor(props) {
  15. super(props);
  16. this.state = {
  17. color: "orange",
  18. };
  19. }
  20. changeColor = () => {
  21. this.setState({
  22. color: this.state.color,
  23. });
  24. };
  25. render() {
  26. let value = { color: this.state.color, changeColor: this.changeColor };
  27. return (
  28. <ThemeContext.Provider value={value}>
  29. <Content />
  30. </ThemeContext.Provider>
  31. );
  32. }
  33. }

使用 方法二: 通过Consumer 接收

  1. function Header() {
  2. return (
  3. <ThemeContext.Consumer>
  4. {(value) => (
  5. <div style={{ border: `5px solid ${value.color}` }}> Header</div>
  6. )}
  7. </ThemeContext.Consumer>
  8. );
  9. }

1-7-2 手写Context

原理是, 将value这个静态属性挂载到Provider上面,使用的时候直接从Provider上读取,老得context也可以,但是每个组件都需要传递,不推荐了已经

  1. import React from "react";
  2. function createContext() {
  3. class Provider extends React.Component {
  4. static value;
  5. constructor(props) {
  6. super(props);
  7. Provider.value = props.value;
  8. }
  9. // componentWillReceiveProps(nextProps,preState){
  10. // Provider.value = nextProps.value;
  11. // }
  12. // 关键是每次变更 都要更新Provider上面的value属性, value作为静态属性 更容易让各个组件获取到实例上的值
  13. static getDerivedStateFromProps(nextProps, preState) {
  14. Provider.value = nextProps.value;
  15. return preState;
  16. }
  17. render() {
  18. return this.props.children;
  19. }
  20. }
  21. class Consumer extends React.Component {
  22. render() {
  23. return this.props.children(Provider.value);
  24. }
  25. }
  26. return {
  27. Provider,
  28. Consumer,
  29. };
  30. }
  31. export default createContext;

1-7-3 高阶组件

就是一个函数 接受一个组件 返回一个新组件 为了更好的复用
高阶组件复用 不是太好用, 不是很完美 包一两层还可以接受

  1. import React from "react";
  2. /**
  3. * @param {} WrapperComponent
  4. * @returns
  5. * 高阶函数 将一个函数作为参数和返回值 在其他语言是不可以的
  6. * 高阶组件 组件可以作为函数的参数和返回值
  7. */
  8. function widthLogger(WrapperComponent){
  9. return class extends React.Component{
  10. componentWillMount(){
  11. this.start = Date.now();
  12. }
  13. componentDidMount(){
  14. console.log('当前组件花费了'+ (Date.now() - this.start) + 'ms');
  15. }
  16. render(){
  17. return <WrapperComponent/>
  18. }
  19. }
  20. }
  21. export { widthLogger }

1-7-4、 render props

一种React组件间使用的一个值为函数的prop共享代码技术, render prop接受一个函数,返回一个react元素并调用它而不是实现自己的渲染逻辑

  1. <DataProver render={data=>(<div>hello {data.target}</div>)}>

1-7-5、 react-redux

1、 react-redux 是什么做了什么事? 1、 简化redux的使用 2、通过高阶组件抽离公共部分,将订阅操作和dispatch和状态通过高阶组件中完成注入,在组件中可直接通过props使用 3、通过Provider将store从根组件注入,在高阶组件中使用

1-7-5-1、 react-redux的使用

cnpm i react-redux -S

index.js

  1. import React from "react";
  2. import ReactDOM from "react-dom";
  3. import { Provider } from "react-redux";
  4. import Count1 from "./components/Count1";
  5. import Count2 from "./components/Count2";
  6. import store from "./store/index"
  7. ReactDOM.render(
  8. <Provider store={store}>
  9. <Count1 />
  10. <hr/>
  11. <Count2/>
  12. </Provider>,
  13. document.getElementById('root')
  14. )

component/Count1.js

  1. import React from "react";
  2. import actions from "../store/actions/counter1";
  3. import { connect } from 'react-redux'
  4. class Count1 extends React.Component {
  5. constructor(props) {
  6. super(props);
  7. this.state = {
  8. number: props.number,
  9. };
  10. }
  11. render() {
  12. return (
  13. <div>
  14. <div>{this.props.number}</div>
  15. <button onClick={()=>this.props.add(5)}>+</button>
  16. <br />
  17. <button onClick={()=>this.props.minus(5)}>-</button>
  18. </div>
  19. );
  20. }
  21. }
  22. let mapStateFromProps = (state) => state.counter1;
  23. export default connect(mapStateFromProps, actions)(Count1);

1-7-5-2、 react-redux手写原理
  • Provder 的逻辑

Provider是一个组件, 包裹了根组件并且接受注入store, 通过react的Context上下文Provider提供

react-redux/Provider.js

  1. import ReactReduxContext from "./ProviderContext";
  2. import React from "react";
  3. export default class extends React.Component {
  4. constructor(props) {
  5. super(props);
  6. }
  7. render() {
  8. return (
  9. <ReactReduxContext.Provider value={{store:this.props.store}}>
  10. {this.props.children}
  11. </ReactReduxContext.Provider>
  12. );
  13. }
  14. }

react-redux/ProviderContext.js

? createContext 中 static contentType 与 this.context 的关系

  1. // import createContext from '../react/Context.js'
  2. import {createContext} from 'react'
  3. let ReactReduxContext = createContext()
  4. export default ReactReduxContext

react-redux/connect.js

connect 是一个高阶组件, 包裹当前组件, 将store的方法和数据在这里拿到操作,并且注入到当前组件的props上
执行两个方法 第一个参数是两个映射的state和第二个参数是actions,
第二个函数的参数是当前组件的名字
mapStateFromProps 将state进行拆分 配合浅比较进行优化

  1. import React from "react";
  2. import ReactReduxContext from "./ProviderContext";
  3. import bindActionCreators from "../redux/bindActionCreators"
  4. /**
  5. *
  6. * @param {} mapStateToProps
  7. * @param {*} actions
  8. * @returns {Function}
  9. * 自方法负责把组件库和仓库进行关联链接
  10. * mapStateToProps 将state进行拆分 配合浅比较进行优化
  11. */
  12. function connect(mapStateToProps, actions) {
  13. return function (WrapComponent) {
  14. return class extends React.Component {
  15. static contextType = ReactReduxContext;
  16. constructor(props, context) {
  17. super(props);
  18. this.state = mapStateToProps(context.store.getState());
  19. if(typeof actions === 'function'){
  20. this.bindAction = actions(context.store.dispatch,props)
  21. }else{
  22. this.bindAction = bindActionCreators(actions, context.store.dispatch);
  23. }
  24. }
  25. componentDidMount() {
  26. this.unsubscribe = this.context.store.subscribe((oldstate,newstate) => this.setState(mapStateToProps(this.context.store.getState())))
  27. }
  28. componentWillUnmount() {
  29. this.unsubscribe();
  30. }
  31. render() {
  32. return <WrapComponent {...this.state} {...this.bindAction}/>;
  33. }
  34. };
  35. };
  36. }
  37. export default connect;

react-redux/index.js

import connect from './connect'
import Provider from './Provider'

export {
    Provider,
    connect
}

component/Count1.js

import React from "react";
import actions from "../store/actions/counter1";
import { connect } from "../react-redux";
import PureComponent from "../react/PureComponent";
import { MINU1 } from "../action-types";

class Count1 extends PureComponent {
  constructor(props) {
    super(props);
    this.state = {
      number: props.number,
    };
  }
  render() {
    console.log("render1");
    return (
      <div>
        <div>{this.props.number}</div>
        <button onClick={() => this.props.add(5)}>+</button>
        <br />
        <button onClick={() => this.props.minus(5)}>-</button>
      </div>
    );
  }
}
//* 使用更简单, 减少无用渲染
let mapStateToProps = (state) => state.counter1;
//* 传入actions对象也可以函数也可以做兼容
let mapDispatchToProps = (dispatch) => ({
  add() {
    dispatch({ type: "ADD1", payload: 1 });
  },
  minus() {
    dispatch({ type: "MINU1", payload: 1 });
  },
});
export default connect(mapStateToProps, mapDispatchToProps)(Count1);

react/PrueComponent

import React from 'react'
export default class PureComponent extends React.Component{
    static isPureComponent = true;
    shouldComponentUpdate(nextProps){
        // 询问组件是否需要刷新
       let oldProps = this.props;
       if( oldProps === null || typeof oldProps !== 'object' || nextProps === null || typeof nextProps !== 'object'){
          return true
       }
       if(Object.keys(oldProps).length !==Object.keys(nextProps).length){
        return true
       }
       for (const oldKey in oldProps) {
           if(!nextProps.hasOwnProperty(oldKey) || nextProps[oldKey] !== oldProps[oldKey] ){
            return true
           }
       }
       return false
    }
}