0

0

0

修罗

站点介绍

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

Context使用&redux使用及实现

修罗 2020-11-22 1951 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)


最新评论

  • https://www.studyglobus.com/author/carroll34a5552/

    Ценники на вывоз мусора сейчас космос, любые лайфхаки по экономии в тему.

  • Richardgrold

    1xbet onlayn kazino platforması ilə vaxtınızı daha səmərəli və maraqlı keçirin. Saytın canlı kazino bölməsi istifadəçilərə fərqli bir dünya təklif edir. Burada canlı dilerli kazino oyunlarının hər bir növünü sınaqdan keçirə bilərsiniz. Krupyelərin peşəkarlığı oyunun dinamikasını və marağını artırır. Müntəzəm olaraq yenilənən oyun siyahısı cansıxıcılığa yol vermir. Hesabınızı artırmaq üçün onlarla fərqli və etibarlı üsul mövcuddur. Qazanılan pullar cəmi bir neçə dəqiqə ərzində şəxsi hesabınıza köçürülür. Aktiv fəaliyyətinizə görə fərdi bonuslar və hədiyyələr qazana bilərsiniz. Dəstək xidməti həm çat, həm də e-poçt vasitəsilə kömək göstərir. 1xbet ailəsinə qoşulun və böyük uduşlar üçün ilk addımı atın. onlayn kazino

  • 砂岩白丘

    我一直梦想, 看到你们相册那样的地方。真棒。

  • 城市地標

    我喜欢, 这里分享真实经验。你的项目 就是 属于这里的。请继续。

  • 宗教地標

    我热爱这样的想法, 去那么多国家。真的很鼓舞。

  • 觀景高地

    能感受到热爱。继续保持 感受。

  • 最佳季節

    我关注这样的资源, 写得很实在。你的网站 就是 属于这里的。加油。

  • 1

    1

  • 1

    1

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

    1

日历

2026年07月

   1234
567891011
12131415161718
19202122232425
262728293031 

文章目录

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