React的上下文-Context

想象你的身影發表於2019-01-02

開場白

19年的第一篇文章,雖然18年也沒有分享多少,但是19年開始,我覺得要好好學習,好好努力。當然新的一年伊始,祝大家在19年平安、幸福,還有發發發。

導語

redux解決的核心問題是父子兄弟等元件件傳值很麻煩的問題,於是有了一個"通訊班"--redux,這個通訊班可以幫我們把元件之間的狀態整合到一起,然後修改也統一修改。我們覺得很nice。上一篇文章我簡單的跟大家解讀了redux的工作原理。但是,修改完成以後,把修改的東西再通知下去我們又會覺得是一個麻煩,我們也希望能有這樣一個通訊班來幫我們把命令傳達下去。為了解決這個問題,react-redux義無反顧的出現了。這樣相對來說就比較完美了。那今天我們就會想把整個工作流都自己來簡單實現了,也便有了接下來的故事。redux、react-redux兄弟同心,齊力傳值。

redux三板斧

redux三板斧,store、action,reducer。 之前簡單了做了一個redux。今天,把上次的程式碼優化下,做個精緻的redux,但是其實裡面的東西都是差不多的。在這一部分的分享我也不會做太詳細的講解,如果過程中有疑問可以看下上篇文章,或者看完後不懂大家可以留言互相交流。 createStore建立的物件擁有4個API,他們分管不同的職能。

React的上下文-Context
程式碼設計成如下:

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]現在也是邊準備春招的實習面試,邊學習,邊寫文章。分享不到位或是不正確的地方望大家指正。

相關文章