React的上下文-Context

开场白

19年的第一篇文章,虽然18年也没有分享多少,但是19年开始,我觉得要好好学习,好好努力。当然新的一年伊始,祝大家在19年平安、幸福,还有发发发。

导语

redux解决的核心问题是父子兄弟等组件件传值很麻烦的问题,于是有了一个"通讯班"--redux,这个通讯班可以帮我们把组件之间的状态整合到一起,然后修改也统一修改。我们觉得很nice。上一篇文章我简单的跟大家解读了redux的工作原理。但是,修改完成以后,把修改的东西再通知下去我们又会觉得是一个麻烦,我们也希望能有这样一个通讯班来帮我们把命令传达下去。为了解决这个问题,react-redux义无反顾的出现了。这样相对来说就比较完美了。那今天我们就会想把整个工作流都自己来简单实现了,也便有了接下来的故事。redux、react-redux兄弟同心,齐力传值。

redux三板斧

redux三板斧,store、action,reducer。 之前简单了做了一个redux。今天,把上次的代码优化下,做个精致的redux,但是其实里面的东西都是差不多的。在这一部分的分享我也不会做太详细的讲解,如果过程中有疑问可以看下上篇文章,或者看完后不懂大家可以留言互相交流。 createStore创建的对象拥有4个API,他们分管不同的职能。

代码设计成如下:

export const createStore = (state,storeChange) => {
    const listeners = [];
    let  store = state || {};
    const subscribe = (listener) => {
        listeners.push(listener)
    }
    const  dispatch = (action) => {
       const newStore = storeChange(store,action);
       store = newStore;
       listeners.forEach((item) => item())
    }
    const getStore = () => {
        return store;
    }
    return {store,dispatch,subscribe,getStore}
}
复制代码

subcribe使用订阅发布者模式。组件订阅了,中央有改变的时候就会发布消息给他。订阅的方式,通过一个监听数组,把每个组件的render函数都放到有个数组里面,当数据改变后,我们把所有订阅了的组件的监听函数重新执行一遍。这样视图层就得到了改变。因此在dispatch的设计思想就是,先把reducer后的值拿过来,把它赋值给中央,再把组件的值更新下。 storeChange.js的代码,使用es6解构语法如下:

export const storeChange = (store,action) => {
    switch (action.type) {
        case "HEAD":
            return {
                ...store,
                head: action.head
            }
        case "BODY":
            return {
                ...store,
                body:action.body
            }
        default:
            return { ...store}
    }
}
复制代码

reudx在大型项目中往往会有很多的输出,因此我们在此也用了一个设计模式,把输出统一,这样便于后期的维护和修改。index.js代码如下:

export *  from './createStore';
export * from './storeChange';
复制代码

好了。来到今天的重磅嘉宾了。你觉得是react-redux。当然不是。而是react-redux的中流砥柱,context。

有请context

Context是React的高级API ,使用context可以实现跨组件传值。在react-redux的中就是通过context提供一个全局的store ,拖拽组件的react-dnd,通过Context在组件中分发DOM的Drg和Drop事件。不仅如此,路由组件react-router还可以通过Context管理路由状态等等,可以说相当重量级的嘉宾了。 这是官方的一个描述:

图片关于context的描述

Context的用法

Context的使用基于生产者消费者模式。
父节点作为Context的生产者,而消费者则是父节点下的所有的节点。父节点通过一个静态属性childContextTypes提供给子组件的Context对象属性,并实现一个实例getChildCotext方法,返回一个Context纯对象。而子组件通过一个静态属性contextTypes声明后,才能访问父组件的context对象属性,否则即使属性名没有写错,拿到的对象也是undefined。 App.js我们代码设计如下:

import React, { Component } from "react";
import PropTypes from "prop-types"
import Body from "./component/body/Body"
import Head from "./component/head/Head"
import { createStore, storeChange} from './redux';
// import './App.css';

class App extends Component {
    static childContextTypes = {
        store: PropTypes.object,
        dispatch: PropTypes.func,
        subscribe: PropTypes.func,
        getStore: PropTypes.func
    }
    getChildContext() {
        const state = {
            head: "我是全局head",
            body: "我是全局body",
            headBtn: "修改head",
            bodyBtn: "修改body"
        }
        const { store,dispatch, subscribe,getStore } = createStore(state,storeChange)
        return { store,dispatch,subscribe,getStore}
    }
  render() {
    return (
      <div className="App">
       <Head />
        <Body />
      </div>
    );
  }
}

export default App;
复制代码

static声明一个ChildContextTypes,顶层组件规定要传给子组件Context对象的属性类型,一个getChildContext函数,返回给子组件一个纯对象 ,子组件中接收,子组件目录结构如下:

子组件目录结构
Body.js

import React, {Component} from 'react';
import Button from '../Button/Button';
import PropTypes from "prop-types";

export default class Body extends Component {
    static contextTypes = {
        store: PropTypes.object,
        subscribe: PropTypes.func,
        getStore: PropTypes.func
    }
    constructor(props) {
        super(props);
        this.state = {};
    }

    componentWillMount () {
        const { subscribe } = this.context;
        this._upState();
        subscribe(()=> this._upState())
    }

    _upState() {
        const { getStore } = this.context;
        this.setState({
            ...getStore()
        })
    }
  render () {
    return (
      <div>
        <div className="body">{this.state.body}</div>
        <Button/>
      </div>
    )
  }
}
复制代码

子组件通过一个contextTypes,用来接收父组件传过来的属性。组件中使用了状态就代表他需要订阅,因此我们加载组件的时候就就组件的setState方法push到监听函数里面,这样就能让数据改变后页面能够得到重新渲染。关于setState这篇文章--setState这个API到底怎么样讲解的挺详细的,不是很明白setState背后的原理的骨子可以看看。 同理贴上Head.js:

import React, {Component} from 'react';
import PropTypes from "prop-types"

export default class Head extends  Component{
    static contextTypes = {
        store: PropTypes.object,
        subscribe: PropTypes.func,
        getStore: PropTypes.func
    }
    constructor(props) {
        super(props);
        this.state = { };
    }
    componentWillMount () {
        const { subscribe } = this.context;
        this._upState();
        subscribe(()=> this._upState())
    }
    _upState() {
        const { getStore } = this.context;
        this.setState({
            ...getStore()
        })
    }
    render() {
        return (
            <div className="head">{this.state.head}</div>
        )
    }

}
复制代码

Button.js

import React, { Component } from 'react';
import PropTypes from 'prop-types';

export default class Button extends Component {
    static contextTypes = {
        store: PropTypes.object,
        dispatch: PropTypes.func,
        subscribe: PropTypes.func,
        getStore: PropTypes.func

    }
    constructor(props) {
        super(props);
        this.state = {};
    }
    componentWillMount() {
        this._upState();
    }

    _upState() {
        const { store } = this.context;
        this.setState({
            ...store
        })
    }
    changeContext(type) {
        const { dispatch } =this.context;
        const key = type === "HEAD" ? "head":"body";
        dispatch({
            type: type,
             [key]: `我是修改后的${key}`
        })
    }

  render () {
    return (
      <div className="button">
        <div className="btn" onClick={() => {
            this.changeContext("HEAD")
        }}>改变head</div>
        <div className="btn" onClick={() => {
            this.changeContext("BODY")
        }}>改变body</div>
      </div>
    )
  }
}
复制代码

整个流程走完,context的API在react-redux的用法就是这样的了 这是效果:

改变前
改变后
数据

欢送context

context总共分为四步:

  • ChildContextTypes => 顶层组件中规定类型
  • getChildContext 顶层组件中设置传递属性
  • 后代组件通过contextTypes 规定数据类型
  • 后代组件this.context获取数据

后期React对Context做了调整,但是更方便我们使用,有需要的可以看下我的github上demo.js,一清二楚。

结束语

其实一步步慢慢去了解,就能发现万事万物真的皆是一理,之前面对两个主流框架,react和vue,大家都说vue更简单,更容易上手,于是就先学了vue,刚学习的时候,感觉,好像也不难,后面在公司实习的时候,一直用,感觉用的挺顺手的。就觉得如果要用起来也就那么回事。再到离职后,发现都在用react,去学react,也是同样的感觉。学习可能是一个坎,坚持下跨过去了就好了。一个大三老油条[hahah]现在也是边准备春招的实习面试,边学习,边写文章。分享不到位或是不正确的地方望大家指正。

猜你喜欢

转载自juejin.im/post/5c2c99d0f265da61736a65be