0

0

0

修罗

站点介绍

只有了解事实才能获得真正的自由

Context使用&redux使用及实现

修罗 2020-11-22 1765 0条评论 react

首页 / 正文

Context使用&redux使用及实现

目标

  1. 掌握Context
  2. 掌握redux使用及实现

context:https://react.docschina.org/docs/context.html#consuming-multiple-contexts

redux:https://www.redux.org.cn/

redux git:https://github.com/reduxjs/redux

一、组件跨层级通信 - Context

1606034868402.png

在一个典型的 React 应用中,数据是通过 props 属性自上而下(由父及子)进行传递的,但这种做法对于某些类型的属性而言是极其繁琐的(例如:地区偏好,UI 主题),这些属性是应用程序中许多组件都需要的。Context 提供了一种在组件之间共享此类值的方式,而不必显式地通过组件树的逐层传递 props。

React中使用Context实现祖代组件后代组件跨层级传值。 Vue中的provide & inject来源于Context。

Context API

React.createContext:创建一个 Context 对象。当 React 渲染一个订阅了这个 Context 对象的组件,这个组件会从组件树中离自身最近的那个匹配的 Provider 中读取到当前的 context 值。

Context.Provider:Provider 接收一个 value 属性,传递给消费组件,允许消费组件订阅 context 的变化。一个 Provider 可以和多个消费组件有对应关系。多个 Provider 也可以嵌套使用,里层的会覆盖外层的数据。

当 Provider 的 value 值发生变化时,它内部的所有消费组件都会重新渲染。Provider 及其内部 consumer 组件都不受制于 shouldComponentUpdate 函数,因此当 consumer 组件在其祖先组件退出更新的情况下也能更新。

Class.contextType:挂载在 class 上的 contextType 属性会被重赋值为一个由 React.createContext() 创建的 Context 对象。这能让你使用 this.context 来消费最近 Context 上的那个值。你可以在任何生命周期中访问到它,包括render 函数中。

你只通过该 API 订阅单一 context。

Context.Consumer :这里,React 组件也可以订阅到 context 变更。这能让你在函数式组件中完成订阅 context。

这个函数接收当前的 context 值,返回一个 React 节点。传递给函数的 value 值等同于往上组件树离这个 context 最近的 Provider 提供的 value 值。如果没有对应的 Provider,value 参数等同于传递给createContext() 的 defaultValue。

使用Context

1、创建context

农民种菜:如果没有匹配到Provider,取值默认值

export const UserContext = React.createContext({ user: "xiuluo" });

1606040642055.png

2、Provider提供值

接收者:批发商批发菜

注:如果把UserContext.Provider注释掉,MyContextClass和MyContextFunction将取不到user值,取默认值xiuluo

import { UserContext } from "./userContext";

return (
  <UserContext.Provider value={{ user: "xiuluogg" }}>
    <MyContextClass />
  </UserContext.Provider>
);

1606040920630.png

3、消费者:组件获取数据

(1)、class组件

import { UserContext } from "./userContext";
class MyContextClass extends Component {
  // static contextType = UserContext;
  render() {
    return <div>{this.context.user}</div>;
  }
}
MyContextClass.contextType = UserContext;
export default MyContextClass;

1606041052791.png

效果:

把MyContextClass挂到app上

1606041212606.png

(2)、函数组件

上面Provide提供值处加上

import MyContextFunction from "./MyContextFunction";

<UserContext.Consumer>
    {(ctx) => <MyContextFunction {...ctx} />}
</UserContext.Consumer>

1606041311863.png

函数组件

export default function MyContextFunction(props) {
  return <div>{props.user}</div>;
}

1606041436684.png

效果

1606041448366.png

消费多个Context

看消费的组件引入的是哪个context

<UserContext.Provider value={{ user: "xiuluogg" }}>
  <MyContextClass />
  <UserContext.Consumer>
    {(ctx) => <MyContextFunction {...ctx} />}
  </UserContext.Consumer>
    
  <ThemeContext.Provider value={{ theme: "blue" }}>
    <!-- ==================== -->
    <MultipleContextsPage />
    <!-- ==================== -->
  </ThemeContext.Provider>
</UserContext.Provider>

MultipleContextsPage

render() {
  return (
    <div>
      <ThemeContext.Consumer>
        {(theme) => (
          <UserContext.Consumer>
            {(user) => <div className={theme.themeColor}>{user.name}</div>}
          </UserContext.Consumer>
        )}
      </ThemeContext.Consumer>
    </div>
  );
}

注意事项

因为 context 会使用参考标识(reference identity)来决定何时进行渲染,这里可能会有一些陷阱,当 provider 的父组件进行重渲染时,可能会在 consumers 组件中触发意外的渲染。举个例子,当每次Provider 重渲染时,以下的代码会重渲染所有下面的 consumers 组件,因为 value 属性总是被赋值为新的对象:

class App extends React.Component {
  render() {
    return (
      <Provider value={{ something: "something" }}>
        <Toolbar />
      </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>
    );
  }
}

总结

在React的官方文档中,Context被归类为高级部分 (Advanced),属于React的高级API,但官方并不建议在稳定版的App中使用Context。

不过,这并不意味着我们不需要关注Context。事实上,很多优秀的React组件都通过Context来完成自己的功能,比如 react-redux的,就是通过Context提供一个 全局态的store,路由组件react-router通过Context管理路由状态等等。在React组件开发中,如果用好Context,可以让你的组件变得强大,而且灵活。

函数组件中可以通过useContext引人上下文

二、Reducer

什么是reducer:https://cn.redux.js.org/docs/basics/Reducers.html

reducer 就是一个纯函数,接收旧的 state 和 action,返回新的 state。

(previousState, action) => newState

之所以将这样的函数称之为 reducer,是因为这种函数与被传入 Array.prototype.reduce(reducer, ? initialValue) 里的回调函数属于相同的类型。保持 reducer 纯净非常重要。永远不要在 reducer 里做这些操作:

  • 修改传入参数;
  • 执行有副作用的操作,如 API 请求和路由跳转;
  • 调用非纯函数,如 Date.now() Math.random()

思考:有如下函数, 聚合成一个函数,并把第一个函数的返回值传递给下一个函数,如何处理。

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;
}

方法:

function compose(...funcs) {
  if (funcs.length === 0) {
    return (arg) => arg;
  }
  if (funcs.length === 1) {
    return funcs[0];
  }
  // 第一次循环返回一个函数(...args) => a(b(...args),
  // 第二次循环a即第一次循环返回的函数(...args) => 上面函数(b(...args))
  return funcs.reduce((a, b) => (...args) => a(b(...args)));
}
console.log(compose(f1, f2, f3)("omg"));

三、Redux 上手

Redux是JavaScript应用的状态容器。它保证程序行为一致性且易于测试。

1606045072139.png

安装redux

npm install redux

redux上手

用户一个累加器举例

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

创建store,src/store/myIndex.js

import { createStore } from "redux";

function counter(state = 1, action) {
  switch (action.type) {
    case "ADD":
      return state + 1;
    case "MINUS":
      return state - 1;
    default:
      return state;
  }
}

export default createStore(counter);

创建MyReduxPage

1606049163417.png

但是提交了action页面没有更新

1606049239906.png

修改订阅

1606049324161.png

此时ok

1606049351160.png

还可以在src/index.js的render里订阅状态变更

const render = () => {
  ReactDom.render(<App />, document.querySelector("#root"));
};
render();
store.subscribe(render);

Redux拓展

核心实现

  • 存储状态state
  • 获取状态getState
  • 更新状态dispatch
  • 变更订阅subscribe

XRedux.js

export function createStore(reducer, enhancer) {
  // 保存状态
  let currentState = undefined;
  // 订阅函数
  let currentSubs = [];

  // 获取状态
  function getState() {
    return currentState;
  }

  // 修改状态
  function dispatch(action) {
    currentState = reducer(currentState, action);
    currentSubs.forEach((cb) => cb());
    return action;
  }

  // 订阅
  function subscribe(cb) {
    currentSubs.push(cb);
  }

  // 确保state有默认值
  dispatch({ type: "@@OOO/KKB-REDUX" });

  return {
    getState,
    dispatch,
    subscribe,
  };
}

store从redux引入的createStore改成上面写的createStore

1606051265032.png

ok

1606051280962.png

异步

Redux只是个纯粹的状态管理器,默认只支持同步,实现异步任务;如延迟,网络请求,需要中间件的支持,如我们试用最简单的redux-thunkredux-logger

中间件就是一个函数,对store.dispatch方法进行改造, 在发出 Action执行Reducer 这两步之间,添加了其他功能。

npm install redux-thunk redux-logger --save

1606051574670.png

应用中间件,store/myIndex.js

切换redux库

import { createStore, applyMiddleware } from "redux";
import logger from "redux-logger";
import thunk from "redux-thunk";
// import { createStore } from "./XRedux";

function counter(state = 1, action) {
  switch (action.type) {
    case "ADD":
      return state + 1;
    case "MINUS":
      return state - 1;
    default:
      return state;
  }
}

export default createStore(counter, applyMiddleware(logger, thunk));

添加按钮触发异步action

1606052945696.png

效果

1606052986641.png

中间件实现

核心任务是实现函数序列执行。 //把下面加入XRedux.js

// applyMiddleware(thunk, logger)(createStore)(reducer)
export function applyMiddleware(...middlewares) {
  return (createStore) => (...args) => {
    const store = createStore(...args);
    let dispatch = store.dispatch;
    const middleApi = {
      getState: store.getState,
      dispatch,
    };
    // 给middleware参数,比如说dispatch
    const middlewaresChain = middlewares.map((middleware) =>
      middleware(middleApi)
    );         
     /**
     * // dispatch函数
     * function dispatch(action) {
        currentState = reducer(currentState, action);
        currentSubs.forEach((cb) => cb());
        return action;
      }

      // middleware(middleApi)执行后的样子
     * (dispatch) => (action) => {
        console.log(action.type + "执行了"); //sy-log
        return dispatch(action);
      }

      (dispatch) => (action) => {
        // action 可以是对象 还可以是函数 ,那不同的形式,操作也不同
        if (typeof action === "function") {
          return action(dispatch, getState);
        } else {
          return dispatch(action);
        }
      }

      执行:compose(...middlewaresChain)(dispatch)后
      log:(action) => {
        console.log(action.type + "执行了"); //sy-log
        return dispatch(action);
      }

      (log) => (action) => {
        // action 可以是对象 还可以是函数 ,那不同的形式,操作也不同
        if (typeof action === "function") {
          return action(dispatch, getState);
        } else {
          return dispatch(action);
        }
      }
     */
    dispatch = compose(...middlewaresChain)(dispatch);
    return {
      ...store,

      // 覆盖上面store里的dispatch
      dispatch,
    };
  };
}

// 返回类似:(x) => f(g(x))
function compose(...funcs) {
  if (funcs.length === 0) {
    return (arg) => arg;
    // return () => {};
  }
  if (funcs.length === 1) {
    return funcs[0];
  }
  return funcs.reduce((a, b) => (...args) => a(b(...args)));
}

redux-logger/redux-thunk实现

使用自己写的中间件

1606053633706.png

function logger({ getState, dispatch }) {
  return (dispatch) => (action) => {
    console.log(action.type + "执行了"); //sy-log
    return dispatch(action);
  };
}

function thunk({ getState, dispatch }) {
  return (dispatch) => (action) => {
    // action 可以是对象 还可以是函数 ,那不同的形式,操作也不同
    if (typeof action === "function") {
      return action(dispatch, getState);
    } else {
      return dispatch(action);
    }
  };
}

测试ok

1606053689451.png

评论(0)


最新评论

  • 1

    1

  • 1

    1

  • -1' OR 2+158-158-1=0+0+0+1 or 'TKCTZnRa'='

    1

  • 1

    1

  • 1

    1

  • 1

    1

  • 1

    1

  • @@5Qa2D

    1

  • 1

    1

  • 1

    1

日历

2025年09月

 123456
78910111213
14151617181920
21222324252627
282930    

文章目录

推荐关键字: Linux webpack js 算法 MongoDB laravel JAVA jquery javase redis