React原理

1、 组件的跨层级通信
React中使⽤用Context实现祖代组件向后代组件跨层级传值
(1) React.createContext
创建Context 对象。
(2) Context.Provider
Provider 接收 value 属性,传递给消费组件,允许消费组件订阅 context 的变化。一个 Provider可以和多个消费组件有对应关系。多个 Provider 也可以嵌套使⽤,⾥层的会覆盖外层的数据。
当 Provider 的 value 值发⽣变化时,它内部的所有消费组件都会重新渲染。
provider变化会引起多个消费组件进行不必要的渲染,浪费性能,要避免。
示例

class App extends React.Component {
    render() {
        return (
            <Provider value={{something: 'something'}}>
                <Toolbar />
            </Provider>
        );
    }
}

将provider的value状态提升到父组件的state里面

class App extends React.Component {
    constructor(props) {
        super(props);
        this.state = {
            value: {something: 'something'},
        };
    }
    render() {
        return (
            <Provider value={this.state.value}>
                <Toolbar />
            </Provider>
        );
    }
}

(3) Class.contextType
挂载在 class 上的 contextType 属性会被重赋值为一个由 React.createContext() 创建的 Context
对象,可以在任何生命周期中访问到它,包括 render 函数中。用法存在局限性。
static contextType = ThemeContext;
[ClassName].contextType = ThemeContext;
(4) Context.Consumer
用于Class型组件订阅 context 变更。
(5) useContext
用于Function型组件订阅 context 变更。接收⼀个 context 对象( React.createContext 的返回值)并返回该 context 的当前值。当前的context 值由上层组件中距离当前组件最近的 <MyContext.Provider> 的 value prop 决定。

// context.js 创建 
import React from "react";
// 定义两个context传两个值 
// provider和comsumer从context里获取
export const ThemeContext = React.createContext({themeColor: "pink"});
export const UserContext = React.createContext();
import React, {Component} from "react";
import ContextTypePage from "./ContextTypePage";
import {ThemeContext, UserContext} from "../Context";
import UseContextPage from "./UseContextPage";
import ConsumerPage from "./ConsumerPage";
export default class ContextPage extends Component {
    constructor(props) {
        super(props);
        this.state = {
            theme: {
                themeColor: "red"
            },
            user: {
                name: "xiaoming"
            }
        };
    }
    changeColor = () => {
        const {themeColor} = this.state.theme;
        this.setState({
            theme: {
                 themeColor: themeColor === "red" ? "green" : "red"
            }
        });
};

    render() {
        const {theme, user} = this.state;
        return (
              <div>
                <h3>ContextPage</h3>
                <button onClick={this.changeColor}>change color</button>
                <ThemeContext.Provider value={theme}>。// context的嵌套
                    <ContextTypePage /> // contextType方法获取
                    <UserContext.Provider value={user}>
                        <UseContextPage /> // 可以消费多个context
                        <ConsumerPage />
                    </UserContext.Provider>
                </ThemeContext.Provider>
                <ContextTypePage />
            </div>
            );
        }
}

pages/ContextTypePage.js

import React, {Component} from "react";
import {ThemeContext} from "../Context";

export default class ContextTypePage extends Component {
    static contextType = ThemeContext; // 静态方法挂载在class下
    render() {
        const {themeColor} = this.context;
        return (
            <div className="border">
              <h3 className={themeColor}>ContextTypePage</h3>
            </div>
        );
    }
}

pages/ConsumerPage.js

import React, {Component} from "react";
import {ThemeContext, UserContext} from "../Context";
// class组件中使用comsumer的方法
export default class ConsumerPage extends Component {
        render() {
            return (
               <div className="border">
                 <ThemeContext.Consumer>
                    // 接收context
                    {themeContext => (
                    <>
                        <h3 className={themeContext.themeColor}>ConsumerPage</h3>
                        <UserContext.Consumer>
                            {userContext => <HandleUserContext {...userContext} />} // 提取出组件
                        </UserContext.Consumer>
                    </>
                )}
                </ThemeContext.Consumer>
            </div>
        );
    }
}
// 防止嵌套太多可以将组件提出来, 接受context参数
    function HandleUserContext(userCtx) {
        return <div>{userCtx.name}</div>;
    }

pages/UseContextPage

import React, {useState, useEffect, useContext} from "react";
import {ThemeContext, UserContext} from "../Context";
// function组件使用context方法
export default function UseContextPage(props) {
    const themeContext = useContext(ThemeContext); // 使用useContext钩子接受context
    const {themeColor} = themeContext; // es6 方法提取出来
    const userContext = useContext(UserContext); 
        return (
            <div className="border">
                <h3 className={themeColor}>UseContextPage</h3> // 在组件中直接使用
                <p>{userContext.name}</p> // 直接使用
            </div>
        );
}

2、高阶组件HOC
高阶组件(HOC)是 React 中用于复用组件逻辑的一种高级技巧。HOC 自身不是 React API 的一部分,它是一种基于 React 的组合特性而形成的设计模式。
高阶组件是参数为组件,返回值为新组件的函数。
组件是将 props 转换为 UI,而高阶组件是将组件转换为另一个组件。

import React, {Component} from "react";
// hoc: 是⼀一个函数,接收⼀一个组件,返回另外⼀一个组件
//这⾥大写开头的Cmp是指function或者class组件
// 定义高阶组件
const foo = Cmp => props => {
    return (
        <div className="border">
          <Cmp {...props} />
        </div>
    );
};
// 定义传入高阶组件的组件
function Child(props) {
    return <div> Child {props.name}</div>;
}
// 组装高阶组件
const Foo = foo(Child);
// 赋值和使用
export default class HocPage extends Component {
    render() {
        return (
          <div>
            <h3>HocPage</h3>
            <Foo name="msg" />
          </div>
        );
    }
}

装饰器写法
利用ES7中出现的装饰器语法,首先安装和配置。
yarn add @babel/plugin-proposal-decorators

更新config-overrides.js

//配置完成后记得重启下
const { addDecoratorsLegacy } = require("customize-cra");
module.exports = override(
    ...,
    addDecoratorsLegacy()//配置装饰器器
);

如果vscode对装饰器有warning,vscode设置里加上
javascript.implicitProjectConfig.experimentalDecorators": true

//HocPage.js
// !装饰器器只能⽤用在class上
// 执⾏行行顺序从下往上
@foo
@foo
class Child extends Component {
    render() {
        return <div> Child {this.props.name}</div>;
    }
}

// const Foo = foo2(foo(foo(Child)));

export default class HocPage extends Component {
    render() {
        return (
          <div>
            <h3>HocPage</h3>
            {/* <Foo name="msg" /> */}
            <Child />
          </div>
        );
    }
}

注意事项:
不要在 render 方法中使用 HOC

render() {
// 每次调⽤用 render 函数都会创建⼀一个新的 EnhancedComponent
// EnhancedComponent1 !== EnhancedComponent2
const EnhancedComponent = enhance(MyComponent);
// 这将导致⼦子树每次渲染都会进⾏行行卸载,和重新挂载的操作!
return <EnhancedComponent />;
}
  1. Redux原理
    (1)reducer
    reducer 就是⼀个纯函数,接收旧的 state 和 action,返回新的 state
;(previousState, action) => newState

称之为 reducer,是因为这种函数与被传⼊ Array.prototype.reduce(reducer, ?initialValue) ⾥的回调函数属于相同的类型

(2) reduce和聚合函数

const array1 = [1, 2, 3, 4];
const reducer = (accumulator, currentValue) => accumulator
+ currentValue;
// 1 + 2 + 3 + 4
console.log(array1.reduce(reducer));
// expected output: 10
// 5 + 1 + 2 + 3 + 4
console.log(array1.reduce(reducer, 5));
// expected output: 15

将以下函数生成聚合函数,把第一个函数的返回值传给下一个函数。

function f1(arg) {
console.log("f1", arg);
return arg;
}
function f2(arg) {
console.log("f2", arg);
return arg;
}
function f3(arg) {
console.log("f3", arg);
return arg;
}
// 普通调用
const  res = f1(f2(f3('omg')));
// output: f3 omg  f2 omg  f1 omg

// 使用聚合函数
function compose(...funcs) {
    if (funcs.length === 0) {
        return arg => arg
    }
    if (funcs.length === 1) {
        return funcs[0]
    }
return funcs.reduce((a, b) => (...args) => a(b(...args)))
}
console.log(compose(f1, f2, f3)("omg"));

(3) redux流程

  1. 需要⼀个store来存储数据
  2. store⾥的reducer初始化state并定义state修改规则
  3. 通过dispatch⼀个action来提交对数据的修改
  4. action提交到reducer函数⾥,根据传⼊的action的type,返回新的state

store存储数据,src/store/index.js

import {createStore} from "redux";
// 初始化 修改状态函数
function countReducer(state = 0, action) {
    switch (action.type) {
        case "ADD":
            return state + 1;
        case "MINUS":
            return state - 1;
        default:
            return state;
    }
}
const store = createStore(countReducer); // 创建store
export default store;

创建ReduxPage

import React, {Component} from "react";
import store from "../store/";
export default class ReduxPage extends Component {
    componentDidMount() {
        store.subscribe(() => { // 变更订阅
            this.forceUpdate();
        });
    }
    add = () => {
        store.dispatch({type: "ADD"}); // dispatch 更改数据,reducer里已经定义好了更改规则
    };
    minus = () => {
        store.dispatch({type: "MINUS"});
    };
    render() {
        return (
          <div>
            <h3>ReduxPage</h3>
            <p>{store.getState()}</p> // getState获取数据
           <button onClick={this.add}>add</button>
            <button onClick={this.minus}>minus</button>
          </div>
        );
    }
}

(3) Redux拓展
实现:
存储状态state
获取状态getState
更新状态dispatch
变更订阅subscribe

// 实现createStore,返回getState,dispatch,subscribe函数
export default function createStore(reducer, enhancer) {
    // ehancer为applyMiddleware返回的结果
    if (enhancer) {
        return enhancer(createStore)(reducer);
    }
    // 函数内存储state的值
    let currentState;
    // 函数内存储监听数组
    let currentListeners = [];
    function getState() {
        return currentState; // 返回当前的值
    }
    function dispatch(action) {
        currentState = reducer(currentState, action); // 用reducer规则更新currentState
        currentListeners.forEach(listener => listener()); 
        //执行每一个listener,告诉subscribe的地方更新currentState(指的是页面重新渲染,展示新的值)
        return action;
    }
    function subscribe(listener) {
        currentListeners.push(listener); // 传入一个listener,加入到监听数组
        return () => {
            currentListeners = []; // 返回一个销毁数组的方法
        };
    }
    dispatch({type: "KKBREDUX/OOOO"});  // 执行reducer中的default
    return {
        getState,
        dispatch,
        subscribe
    };
}

(5) Redux中间件
Redux只是个纯粹的状态管理器,默认只⽀持同步,实现异步任务 ⽐如延
迟,⽹络请求,需要中间件的⽀持,⽐如我们使⽤最简单的redux-thunk和
redux-logger 。


image.png

应用中间价 store.js

import { createStore, applyMiddleware } from "redux";
import logger from "redux-logger";
import thunk from "redux-thunk";
import counterReducer from './counterReducer'
const store = createStore(counterReducer,applyMiddleware(thunk, logger));
    asyAdd = () => {
        // 实现一个中间件使dispatch能够接收函数
        store.dispatch((dispatch, getState) => {
        setTimeout(() => {
        // console.log("now ", getState()); //sy-log
        dispatch({type: "ADD", payload: 1});
        }, 1000);
    });
};

实现 applyMiddleware

export default function applyMiddleware(...middlewares) {
    return createStore => reducer => {
        // 从 createStore(reducer)里拿到dispatch,getState
        const store = createStore(reducer);
        let dispatch = store.dispatch;
       // 柯里化 先传入参数,闭包保存
        const midApi = {
            getState: store.getState,
            dispatch: (action, ...args) => dispatch(action,
            ...args)
        };
    // 先传入参数执行
    const middlewareChain = middlewares.map(middleware =>
    middleware(midApi));
    // 聚合函数加强dispatch
    dispatch = compose(...middlewareChain)(store.dispatch);
    return {
        ...store,
        // 加强版的dispatch
        dispatch
        };
    };
}
function compose(...funcs) {
    if (funcs.length === 0) {
        return arg => arg;
    }
    if (funcs.length === 1) {
        return funcs[0];
    }
    return funcs.reduce((a, b) => (...args) =>
    a(b(...args)));
}

实现redux-logger

function logger({getState}) {
    // action 在调用加强后dispach时传进来的 dispatch({type:'ADD'})
    // next 表示聚合函数 f1( f2(...args) ) 中的 返回值,会被外层调用
    return next => action => {
        console.log("====================================");
        console.log(action.type + "执⾏了!"); //sy-log
        const prevState = getState();
        console.log("prev state", prevState); //sy-log
        const returnValue = next(action);
        const nextState = getState();
        console.log("next state", nextState); //sy-log
        console.log("====================================");
        return returnValue;
    };
}

实现 redux-thunk

function thunk({dispatch, getState}) {
    return next => action => {
        // 增加了处理函数的能力
        if (typeof action === "function") {
            return action(dispatch, getState);
        }
        return next(action);
    };
}

实现redux-promis

function promise({dispatch}) {
    return next => action => {
        return isPromise(action) ? action.then(dispatch) :
        next(action);
    };
}
  1. react-redux
    (1) react-hooks

useReducer
useState 的替代方案。它接收一个形如 (state, action) => newState 的 reducer,并返回当前的 state 以及与其配套的 dispatch 方法。
在某些场景下,useReducer 会比 useState 更适用,例如 state 逻辑较复杂且包含多个子值,或者下一个 state 依赖于之前的 state 等。并且,使用 useReducer 还能给那些会触发深更新的组件做性能优化,因为你可以向子组件传递 dispatch 而不是回调函数

const initialState = {count: 0};

function reducer(state, action) {
  switch (action.type) {
    case 'increment':
      return {count: state.count + 1};
    case 'decrement':
      return {count: state.count - 1};
    default:
      throw new Error();
  }
}

function Counter() {
  const [state, dispatch] = useReducer(reducer, initialState);
  return (
    <>
      Count: {state.count}
      <button onClick={() => dispatch({type: 'decrement'})}>-</button>
      <button onClick={() => dispatch({type: 'increment'})}>+</button>
    </>
  );
}

useCallback

// 返回一个依赖于参数a,b执行的回调函数 memoizedCallback
const memoizedCallback = useCallback(
    () => {
      doSomething(a, b);
    },
    [a, b],
);

useMemo
把“创建”函数和依赖项数组作为参数传入 useMemo,它仅会在某个依赖项改变时才重新计算 memoized 值。这种优化有助于避免在每次渲染时都进行高开销的计算。

const memoizedValue = useMemo(() => computeExpensiveValue(a, b), [a, b]);

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

(2) 使用react-redux
在redux的基础上添加了两个api

  1. Provider 为后代组件提供store , 替代getState方法获取数据,使用 useContext
  2. connect 为组件提供数据和变更方法,替代subscribe方法,执行绑定数据和更新组件功能

connect([mapStateToProps], [mapDispatchToProps], [mergeProps], [options])

参数

  • mapStateToProps(state, [ownProps]): stateProps ] (Function)
    该回调函数必须返回一个纯对象,这个对象会与组件的 props 合并。如果定义该参数,组件将会监听 Redux store 的变化,否则 不监听。
    ownProps 是当前组件自身的props,如果指定了,那么只要组件接收到新的 props,mapStateToProps 就会被调用,mapStateToProps 都会被重新计算, mapDispatchToProps 也
    会被调用。注意性能!
  • mapDispatchToProps(dispatch, [ownProps]): dispatchProps ] (Object or Function):
    如果省略这个 mapDispatchToProps 参数,dispatch 会注入到你的组件 props
    中。可传递对象和函数,见示例。

全剧提供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 {Provider} from "./kReactRedux";
import store from "./store/";
// 把Provider放在根组件外层,使子组件能获得store
ReactDOM.render(
    <Provider store={store}>
        <App />
    </Provider>,
    document.getElementById("root")
);
import React, {Component} from "react";
import {connect} from "react-redux";
import {bindActionCreators} from "redux";
// import {bindActionCreators, connect} from "../kReactRedux";
// connect用于连接React组件与store, 返回一个新的已经与store连接的组件类(HOC)

export default connect(
    // 第一个参数 mapStateToProps, state 从 store中获取值
    (state, ownProps) => {
        return {
            count: state.count
        };
    },
    // 第二个参数mapDispatchToProps,可以是 Object / Fucntion
    // Object形式
   {
     add: () => ({type: "ADD"}),
     minus: () => ({type: "MINUS"})
   }

   // Fucntion形式,参数是dispatch与ownProps
   // 使用 bindActionCreators 函数, 作用是绑定dispatch和creators
  (dispatch, ownProps) => {
      let creators = {
        add: payload => ({type: "ADD", payload}),
        minus: () => ({type: "MINUS"})
      };
    creators = bindActionCreators(creators, dispatch);
    return {dispatch, ...creators};
  }
   // 不使用
  (dispatch, ownProps) => {
      return {
          add: payload => dispatch({type: "ADD", payload}),
          minus: () => dispatch({type: "MINUS"})
    }
  };
  }
)(

class ReactReduxPage extends Component {
    add = () => {
      this.props.dispatch({type: "ADD"});
    };
    render() {
      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

import React, {useContext, useReducer, useLayoutEffect} from "react";
const Context = React.createContext();
export const connect = (
    mapStateToProps = state => state,
    mapDispatchToProps
) => WrappendComponent => props => {
    // 定义全局context存储store
    const store = useContext(Context);
    const {dispatch, getState, subscribe} = store;
    // 得到stateProps对象, 直接传入组件
    const stateProps = mapStateToProps(getState());
    let dispatchProps = {dispatch};
    // hooks实现forceupdate
    const [ignored, forceUpdate] = useReducer(x => x + 1, 0);
    
    if (typeof mapDispatchToProps === "function") {
        dispatchProps = mapDispatchToProps(dispatch);
    } else if (typeof mapDispatchToProps === "object") {
        dispatchProps = bindActionCreators(mapDispatchToProps, dispatch);
    }
    useLayoutEffect(() => {
    const unsubscribe = subscribe(() => {
        forceUpdate();
    });
    return () => {
        if (unsubscribe) {
            unsubscribe();
        }
    };
  }, [store]);
  return <WrappendComponent {...props} {...stateProps} {...dispatchProps} />;
};

export function Provider({store, children}) {
    return <Context.Provider value={store}>{children}</Context.Provider>;
}

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

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

(3) react-redux hooks API及实现
useSelector 获取store state
useDispatch 获取dispatch

import React, {useCallback} from "react";
import {useSelector, useDispatch} from "react-redux";
export default function ReactReduxHookPage({value}) {
    const dispatch = useDispatch();
    const add = useCallback(() => {
        dispatch({type: "ADD"});
    }, []);
    const count = useSelector(({count}) => count);
    return (
        <div>
          <h3>ReactReduxHookPage</h3>
          <p>{count}</p>
          <button onClick={add}>add</button>
        </div>
    );
}

实现

export function useSelector(selector) {
    const store = useStore();
    const {getState, subscribe} = store;
    const selectedState = selector(getState());
    const [ignored, forceUpdate] = useReducer(x => x + 1, 0);
    useLayoutEffect(() => {
        const unsubscribe = subscribe(() => {
        forceUpdate();
        });
        return () => {
           if (unsubscribe) {
              unsubscribe();
           }
        };
    }, [store]);
    return selectedState;
}

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

export function useStore() {
    const store = useContext(Context);
    return store;
}
  1. React-router
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,402评论 6 499
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,377评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,483评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,165评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,176评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,146评论 1 297
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,032评论 3 417
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,896评论 0 274
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,311评论 1 310
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,536评论 2 332
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,696评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,413评论 5 343
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,008评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,659评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,815评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,698评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,592评论 2 353

推荐阅读更多精彩内容