亚洲中字慕日产2020,大陆极品少妇内射AAAAAA,无码av大香线蕉伊人久久,久久精品国产亚洲av麻豆网站

資訊專欄INFORMATION COLUMN

高階組件 + New Context API = ?

Joyven / 2869人閱讀

摘要:但是老版的存在一個(gè)嚴(yán)重的問(wèn)題子孫組件可能不更新。其中,某一時(shí)刻屬性發(fā)生變化導(dǎo)致組件觸發(fā)了一次渲染,但是由于組件是且并未用到屬性,所以的變化不會(huì)觸發(fā)及其子孫組件的更新,導(dǎo)致組件未能得到及時(shí)的更新。

1. 前言

繼上次小試牛刀嘗到高價(jià)組件的甜頭之后,現(xiàn)已深陷其中無(wú)法自拔。。。那么這次又會(huì)帶來(lái)什么呢?今天,我們就來(lái)看看【高階組件】和【New Context API】能擦出什么火花!

2. New Context API

Context API其實(shí)早就存在,大名鼎鼎的redux狀態(tài)管理庫(kù)就用到了它。合理地利用Context API,我們可以從Prop Drilling的痛苦中解脫出來(lái)。但是老版的Context API存在一個(gè)嚴(yán)重的問(wèn)題:子孫組件可能不更新。

舉個(gè)栗子:假設(shè)存在組件引用關(guān)系A(chǔ) -> B -> C,其中子孫組件C用到祖先組件A中Context的屬性a。其中,某一時(shí)刻屬性a發(fā)生變化導(dǎo)致組件A觸發(fā)了一次渲染,但是由于組件B是PureComponent且并未用到屬性a,所以a的變化不會(huì)觸發(fā)B及其子孫組件的更新,導(dǎo)致組件C未能得到及時(shí)的更新。

好在React@16.3.0中推出的New Context API已經(jīng)解決了這一問(wèn)題,而且在使用上比原來(lái)的也更優(yōu)雅。因此,現(xiàn)在我們可以放心大膽地使用起來(lái)。說(shuō)了那么多,都不如一個(gè)實(shí)際的例子來(lái)得實(shí)在。Show me the code:

// DemoContext.js
import React from "react";
export const demoContext = React.createContext();

// Demo.js
import React from "react";
import { ThemeApp } from "./ThemeApp";
import { CounterApp } from "./CounterApp";
import { demoContext } from "./DemoContext";

export class Demo extends React.PureComponent {
  state = { count: 1, theme: "red" };
  onChangeCount = newCount => this.setState({ count: newCount });
  onChangeTheme = newTheme => this.setState({ theme: newTheme });
  render() {
    console.log("render Demo");
    return (
      
        
        
      
    );
  }
}

// CounterApp.js
import React from "react";
import { demoContext } from "./DemoContext";

export class CounterApp extends React.PureComponent {
  render() {
    console.log("render CounterApp");
    return (
      

This is Counter application.

); } } class Counter extends React.PureComponent { render() { console.log("render Counter"); return ( {data => { const { count, onChangeCount } = data; console.log("render Counter consumer"); return (
{count}
); }}
); } } // ThemeApp.js import React from "react"; import { demoContext } from "./DemoContext"; export class ThemeApp extends React.PureComponent { render() { console.log("render ThemeApp"); return (

This is Theme application.

); } } class Theme extends React.PureComponent { render() { console.log("render Theme"); return ( {data => { const {theme, onChangeTheme} = data; console.log("render Theme consumer"); return (
); }} ); } }

雖說(shuō)一上來(lái)就貼個(gè)百來(lái)行代碼的這種行為有點(diǎn)low,但是為了介紹New Context API的基本用法,也只能這樣了。。。不過(guò)啊,上面的例子其實(shí)很簡(jiǎn)單,就算是先對(duì)New Context API的使用方法來(lái)個(gè)簡(jiǎn)單的科普吧~

仔細(xì)觀察上面的代碼不難發(fā)現(xiàn)組件間的層級(jí)關(guān)系,即:Demo -> CounterApp -> Counter 和 Demo -> ThemeApp -> Theme,且中間組件CounterApp和CounterApp并沒(méi)有作為媒介來(lái)傳遞count和theme值。接下來(lái),我們就來(lái)分析下上面的代碼,看看如何使用New Context API來(lái)實(shí)現(xiàn)祖先->子孫傳值的:

New Context API在React中提供了一個(gè)React.createContext方法,它返回的對(duì)象中包含了ProviderConsumer兩個(gè)方法。也就是DemoContext.js中的代碼。

顧名思義,Provider可以理解為公用值的一個(gè)提供者,而Consumer就是這個(gè)公用值的消費(fèi)者。那么兩者是如何聯(lián)系起來(lái)的呢?注意Provider接收的value參數(shù)。Provider會(huì)將這個(gè)value原封不動(dòng)地傳給Consumer,這點(diǎn)也可以從Demo.js/CounterApp.js/ThemeApp.js三個(gè)文件中體現(xiàn)出來(lái)。

再仔細(xì)觀察例子中的value參數(shù),它是一個(gè)對(duì)象,key分別是count, theme, onChangeCount, onChangeTheme。很顯然,在Consumer中,我們不但可以使用count和theme,還可以使用onChangeCount和onChangeTheme來(lái)分別修改相應(yīng)的state,從而導(dǎo)致整個(gè)應(yīng)用狀態(tài)的更新和重新渲染。

下面我們?cè)賮?lái)看看實(shí)際運(yùn)行效果。從下圖中我們可以清楚地看到,CounterApp中的number和ThemeApp中的color都能正常地響應(yīng)我們的操作,說(shuō)明New Context API確實(shí)達(dá)到了我們預(yù)期的效果。除此之外,不妨再仔細(xì)觀察console控制臺(tái)的輸出。當(dāng)我們更改數(shù)字或顏色時(shí)我們會(huì)發(fā)現(xiàn),由于CounterApp和ThemeApp是PureComponent,且都沒(méi)有使用count和theme,所以它們并不會(huì)觸發(fā)render,甚至Counter和Theme也沒(méi)有重新render。但是,這卻并不影響我們Consumer中的正常渲染。所以啊,上文提到Old Context API的子孫組件可能不更新的這個(gè)遺留問(wèn)題算是真的解決了~~~

3. 說(shuō)好的高階組件呢?

通過(guò)上面“生動(dòng)形象”的例子,想必大家都已經(jīng)領(lǐng)會(huì)到New Context API的魔力,內(nèi)心是不是有點(diǎn)蠢蠢欲動(dòng)?因?yàn)橛辛薔ew Context API,我們似乎不需要再借助redux也能創(chuàng)建一個(gè)store來(lái)管理狀態(tài)了(而且還是區(qū)域級(jí),不一定非得在整個(gè)應(yīng)用的最頂層)。當(dāng)然了,這里并非是說(shuō)redux無(wú)用,只是提供狀態(tài)管理的另一種思路。

咦~文章的標(biāo)題不是高階組件 + New Context API = ?嗎,怎么跑偏了?說(shuō)好的高階組件呢?

別急,上面的只是開(kāi)胃小菜,普及New Context API的基本使用方法而已。。。正菜這就來(lái)了~ 文章開(kāi)頭就說(shuō)最近沉迷高階組件無(wú)法自拔,所以在寫(xiě)完上面的demo之后就想著能不能用高階組件再封裝一層,這樣使用起來(lái)可以更加順手。你別說(shuō),還真搞出了一套。。。我們先來(lái)分析上面demo中存在的問(wèn)題:

我們?cè)谕ㄟ^(guò)Provider傳給Consumer的value中寫(xiě)了兩個(gè)函數(shù)onChangeCount和onChangeTheme。但是這里是不是有問(wèn)題?假如這個(gè)組件足夠復(fù)雜,有20個(gè)狀態(tài)難道我們需要寫(xiě)20個(gè)函數(shù)分別一一對(duì)應(yīng)更新相應(yīng)的狀態(tài)嗎?

注意使用到Consumer的地方,我們把所有的邏輯都寫(xiě)在一個(gè)data => {...}函數(shù)中了。假如這里的組件很復(fù)雜怎么辦?當(dāng)然了,我們可以將{...}這段代碼提取出來(lái)作為Counter或Theme實(shí)例的一個(gè)方法或者再封裝一個(gè)組件,但是這樣的代碼寫(xiě)多了之后,就會(huì)顯得重復(fù)。而且還有一個(gè)問(wèn)題是,假如在Counter或Theme的其他實(shí)例方法中想獲取data中的屬性和update方法怎么辦?

為了解決以上提出的兩個(gè)問(wèn)題,我要開(kāi)始裝逼了。。。

3.1 Provider with HOC

首先,我們先來(lái)解決第一個(gè)問(wèn)題。為此,我們先新建一個(gè)ContextHOC.js文件,代碼如下:

// ContextHOC.js
import React from "react";

export const Provider = ({Provider}, store = {}) => WrappedComponent => {
  return class extends React.PureComponent {
    state = store;
    updateContext = newState => this.setState(newState);
    render() {
      return (
        
          
        
      );
    }
  };
};

由于我們的高階組件需要包掉Provider層的邏輯,所以很顯然我們返回的組件是以Provider作為頂層的一個(gè)組件,傳進(jìn)來(lái)的WrappedComponent會(huì)被包裹在Provider中。除此之外還可以看到,Provider會(huì)接收兩個(gè)參數(shù)Provider和initialVlaue。其中,Provider就是用React.createContext創(chuàng)建的對(duì)象所提供的Provider方法,而store則會(huì)作為state的初始值。重點(diǎn)在于Provider的value屬性,除了state之外,我們還傳了updateContext方法。還記得問(wèn)題一么?這里的updateContext正是解決這個(gè)問(wèn)題的關(guān)鍵,因?yàn)镃onsumer可以通過(guò)它來(lái)更新任意的狀態(tài)而不必再寫(xiě)一堆的onChangeXXX的方法了~

我們?cè)賮?lái)看看經(jīng)過(guò)Provider with HOC改造之后,調(diào)用方應(yīng)該如何使用??创a:

// DemoContext.js
import React from "react";
export const store = { count: 1, theme: "red" };
export const demoContext = React.createContext();

// Demo.js
import React from "react";

import { Provider } from "./ContextHOC";
import { ThemeApp } from "./ThemeApp";
import { CounterApp } from "./CounterApp";
import { store, demoContext } from "./DemoContext";

@Provider(demoContext, store)
class Demo extends React.PureComponent {
  render() {
    console.log("render Demo");
    return (
      
); } }

咦~ 原來(lái)與Provider相關(guān)的代碼在我們的Demo中全都不見(jiàn)了,只有一個(gè)@Provider裝飾器,想要公用的狀態(tài)全都寫(xiě)在一個(gè)store中就可以了。相比原來(lái)的Demo,現(xiàn)在的Demo組件只要關(guān)注自身的邏輯即可,整個(gè)組件顯然看起來(lái)更加清爽了~

3.2 Consumer with HOC

接下來(lái),我們?cè)賮?lái)解決第二個(gè)問(wèn)題。在ContextHOC.js文件中,我們?cè)賹?dǎo)出一個(gè)Consumer函數(shù),代碼如下:

export const Consumer = ({Consumer}) => WrappedComponent => {
  return class extends React.PureComponent {
    render() {
      return (
        
          {data => }
        
      );
    }
  };
};

可以看到,上面的代碼其實(shí)非常簡(jiǎn)單。。。僅僅是利用高階組件給WrappedComponent多傳了一個(gè)context屬性而已,而context的值則正是Provider傳過(guò)來(lái)的value。那么這樣寫(xiě)有什么好處呢?我們來(lái)看一下調(diào)用的代碼就知道了~

// CounterApp.js
import React from "react";
import { Consumer } from "./ContextHOC";
import { demoContext } from "./DemoContext";

const MAP = { add: { delta: 1 }, minus: { delta: -1 } };

// ...省略CounterApp組件代碼,與前面相同

@Consumer(demoContext)
class Counter extends React.PureComponent {

  onClickBtn = (type) => {
    const { count, updateContext } = this.props.context;
    updateContext({ count: count + MAP[type].delta });
  };

  render() {
    console.log("render Counter");
    return (
      
{this.props.context.count}
); } } // ThemeApp.js import React from "react"; import { Consumer } from "./ContextHOC"; import { demoContext } from "./DemoContext"; // ...省略ThemeApp組件代碼,與前面相同 @Consumer(demoContext) class Theme extends React.PureComponent { onChangeTheme = evt => { const newTheme = evt.target.value; const { theme, updateContext } = this.props.context; if (newTheme !== theme) { updateContext({ theme: newTheme }); } }; render() { console.log("render Theme"); return (
) } }

可以看到,改造之后的Counter和Theme代碼一定程度上實(shí)現(xiàn)了去Consumer化。因?yàn)楹虲onsumer相關(guān)的邏輯僅剩一個(gè)@Consumer裝飾器了,而且我們只要提供和祖先組件中Provider配對(duì)的Consumer就可以了。相比最初的Counter和Theme組件,現(xiàn)在的組件也是更加清爽了,只需關(guān)注自身的邏輯即可。

不過(guò)需要特別注意的是,現(xiàn)在想要獲取Provider提供的公用狀態(tài)值時(shí),改成了從this.props.context中獲??;想要更新?tīng)顟B(tài)的時(shí)候,調(diào)用this.props.context.updateContext即可。

為什么?因?yàn)橥ㄟ^(guò)@Consumer裝飾的組件Counter和Theme現(xiàn)在就是ContextHOC文件中的那個(gè)WrappedComponent,我們已經(jīng)把Provider傳下來(lái)的Value作為context屬性傳給它了。所以,我們?cè)俅瓮ㄟ^(guò)高階組件簡(jiǎn)化了操作~

下面我們?cè)賮?lái)看看使用高階組件改造過(guò)后的代碼看看運(yùn)行的效果。

3.3 優(yōu)化

你以為文章到這里就要結(jié)束了嗎?當(dāng)然不是,寫(xiě)論文的套路不都還要提出個(gè)優(yōu)化方法然后做實(shí)驗(yàn)比較么~ 更何況上面這張圖有問(wèn)題。。。

沒(méi)錯(cuò),通過(guò)ContextHOC改造過(guò)后,上面的這張運(yùn)行效果圖似乎看上去沒(méi)有問(wèn)題,但是仔細(xì)看Console控制臺(tái)的輸出你就會(huì)發(fā)現(xiàn),當(dāng)更新count或theme任意其中一個(gè)的時(shí)候,Counter和Theme都重新渲染了一次?。?!可是,我的Counter和Theme組件明明都已經(jīng)是PureComponent了啊~ 為什么沒(méi)有用?。?!

原因很簡(jiǎn)單,因?yàn)槲覀儌鹘oWrappedComponent的context每次都是一個(gè)新對(duì)象,所以就算你的WrappedComponent是PureComponent也無(wú)濟(jì)于事。。。那么怎么辦呢?其實(shí),上文中的Consumer with HOC操作非常粗糙,我們直接把Provider提供的value值直接一股腦兒地傳給了WrappedComponent,而不管WrappedComponent是否真的需要。因此,只要我們對(duì)傳給WrappedComponent的屬性值精細(xì)化控制,不傳不相關(guān)的屬性就可以了。來(lái)看看改造后的Consumer代碼:

// ContextHOC.js
export const Consumer = ({Consumer}, relatedKeys = []) => WrappedComponent => {
  return class extends React.PureComponent {
    _version = 0;
    _context = {};
    getContext = data => {
      if (relatedKeys.length === 0) return data;
      [...relatedKeys, "updateContext"].forEach(k => {
        if(this._context[k] !== data[k]) {
          this._version++;
          this._context[k] = data[k];
        }
      });
      return this._context;
    };
    render() {
      return (
        
          {data => {
            const newContext = this.getContext(data);
            const newProps = { context: newContext, _version: this._version, ...this.props };
            return ;
          }}
        
      );
    }
  };
};

// 別忘了給Consumer組件指定relatedKeys

// CounterApp.js
@Consumer(demoContext, ["count"])
class Counter extends React.PureComponent {
  // ...省略
}

// ThemeApp.js
@Consumer(demoContext, ["theme"])
class Theme extends React.PureComponent {
  // ...省略
}

相比于第一版的Consumer函數(shù),現(xiàn)在這個(gè)似乎復(fù)雜了一點(diǎn)點(diǎn)。但是其實(shí)還是很簡(jiǎn)單,核心思想剛才上面已經(jīng)說(shuō)了,這次我們會(huì)根據(jù)relatedKeys從Provider傳下來(lái)的value中匹配出WrappedComponent真正想要的屬性。而且,為了保證傳給WrappedComponent的context值不再每次都是一個(gè)新對(duì)象,我們將它保存在了組件的實(shí)例上。另外,只要Provider中某個(gè)落在relatedKeys中的屬性值發(fā)生變化,this._version值就會(huì)發(fā)生變化,從而也保證了WrappedComponent能夠正常更新。

最后,我們?cè)賮?lái)看下經(jīng)過(guò)優(yōu)化后的運(yùn)行效果。

4. 寫(xiě)在最后

經(jīng)過(guò)今天這波操作,無(wú)論是對(duì)New Context API還是HOC都有了更深一步的理解和運(yùn)用,所以收貨還是挺大的。最重要的是,在現(xiàn)有項(xiàng)目不想引進(jìn)redux和mobx的前提下,本文提出的這種方案似乎也能在一定程度上解決某些復(fù)雜組件的狀態(tài)管理問(wèn)題。

當(dāng)然了,文中的代碼還有很多不嚴(yán)謹(jǐn)?shù)牡胤?,還需要繼續(xù)進(jìn)一步地提升。完整代碼在這兒,歡迎指出不對(duì)或者需要改進(jìn)的地方。

文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。

轉(zhuǎn)載請(qǐng)注明本文地址:http://www.ezyhdfw.cn/yun/98376.html

相關(guān)文章

  • react new context API的一次實(shí)踐(補(bǔ)充)

    摘要:作為數(shù)據(jù)的發(fā)布方,它擁有一個(gè)名為的屬性,用于維護(hù)數(shù)據(jù)內(nèi)容,通過(guò)傳遞給的數(shù)據(jù)會(huì)被發(fā)布出去。最后,組件將自己的原封不動(dòng)的傳遞給。但是通過(guò)這次的實(shí)踐,也算是熟悉的的用法,對(duì)也加深了了解吧。 這是一篇我發(fā)在掘金上的文章,原文有一個(gè)我沒(méi)有解決的問(wèn)題,在網(wǎng)友的解答下我找到了答案,我把文章重新修改編輯后,同步發(fā)送到這里,希望能對(duì)大家有所幫助。本文原發(fā)布于掘金:https://juejin.im/po...

    kaka 評(píng)論0 收藏0
  • 【React進(jìn)階系列】手寫(xiě)實(shí)現(xiàn)react-redux api

    簡(jiǎn)介:簡(jiǎn)單實(shí)現(xiàn)react-redux基礎(chǔ)api react-redux api回顧 把store放在context里,所有子組件可以直接拿到store數(shù)據(jù) 使組件層級(jí)中的 connect() 方法都能夠獲得 Redux store 根組件應(yīng)該嵌套在 中 ReactDOM.render( , rootEl ) ReactDOM.render( ...

    劉玉平 評(píng)論0 收藏0
  • React組件設(shè)計(jì)技巧

    摘要:我們可以通過(guò)剛剛高階函數(shù)的思想來(lái)創(chuàng)建一個(gè)中間組件,也就是我們說(shuō)的高階組件。僅傳遞組件所需要的屬性。在受控組件中,表單數(shù)據(jù)由組件負(fù)責(zé)處理。作為頂層組件接收一個(gè)名為的,可以接收任意需要被放入中的字符串,數(shù)字,甚至是函數(shù)。 React組件設(shè)計(jì) 組件分類 展示組件和容器組件 展示組件 容器組件 關(guān)注事物的展示 關(guān)注事物如何工作 可能包含展示和容器組件,并且一般會(huì)有DOM標(biāo)簽和cs...

    Luosunce 評(píng)論0 收藏0
  • React的組件模式

    摘要:有狀態(tài)組件通常使用和生命周期相關(guān)事件。組件模式是使用時(shí)的最佳實(shí)踐,最初引入組件模式是為了將數(shù)據(jù)邏輯和表現(xiàn)層進(jìn)行分離。這是一種可以對(duì)輸入組件的進(jìn)行修改增刪改查然后返回全新的修改后的組件強(qiáng)大模式,想想和。 showImg(https://segmentfault.com/img/bVborAV?w=2560&h=1440); 想閱讀更多優(yōu)質(zhì)文章請(qǐng)猛戳GitHub博客,一年百來(lái)篇優(yōu)質(zhì)文章等著...

    Muninn 評(píng)論0 收藏0

發(fā)表評(píng)論

0條評(píng)論

最新活動(dòng)
閱讀需要支付1元查看
<