react new context API的一次實踐

七秋發表於2018-02-28

最近接到一個簡單的內部專案,邏輯並不複雜,就想著 不用redux了,用react的new context API試試看,折騰了兩天,把過程和感想跟大家分享下。

由於是公司的專案,所以下文的示例程式碼都是我重新寫的,望見諒!

基本用法

先讓我們來看一下這個介面的基本用法: 使用它首先需要使用react的createContext方法建立一個例項:

import React, { createContext } from "react";
const Context = createContext();
複製程式碼

Context例項提供兩個元件:Provider和Consumer

const ContextProvider = Context.Provider;
const ContextConsumer = Context.Consumer;

複製程式碼

其中ContextProvider是資料的釋出方,而ContextConsumer是資料的訂閱方。

<ContextProvider value={{ name: "A", age: 18 }}>
    <ContextConsumer>
        {
            context => (
                <div>
                    name:&nbsp;
                    <span>{context.name}</span>
                    &nbsp;age:&nbsp;
                    <span>{context.age}</span>
                </div>
            )
        }
    </ContextConsumer>
</ContextProvider>
複製程式碼

ContextProvider是資料的釋出方。它擁有一個名為value的屬性,用於維護資料內容,通過value傳遞給ContextProvider的資料會被髮布出去。 而ContextConsumer是資料的訂閱方,它的props.children是一個函式,接收的引數是被髮布的資料,我們通過呼叫這個函式來獲取被ContextProvider釋出的資料,並且返回我們想要渲染的元件。 在這個示例中,我們最後在頁面上能夠顯示出name: A age: 18

關於呼叫createContext方法時候的傳參,理論上第一個傳參應該是初始化的資料,但是我使用後發現,如果在ContextProvider的value屬性中不傳入對應的屬性的話,無法在ContextConsumer中獲取到那個初始化的屬性。

const Context = createContext({ name: "A" });
<Context.Provider value={{ age: 18 }}>
    <Context.Consumer>
        {
            context => (
                <div>
                    name:&nbsp;
                    <span>{context.name}</span>
                    &nbsp;age:&nbsp;
                    <span>{context.age}</span>
                </div>
            )
        }
    </Context.Consumer>
</Context.Provider>
複製程式碼

這個例子中,name的值無法被獲取到。 我目前還沒找到原因,如果知道的朋友請告訴我一下,謝謝!

補充

關於我這個問題的答案,感謝 @我很西瓜的瓜 朋友的解答,我去試了下,果然是這樣: 只有當Provider不被使用,也就是Context僅僅被用作訂閱資料而不是用來發布資料的時候,呼叫createContext()方法時傳遞的資料才會被當作當作源資料釋出;而使用Provider來發布資料的時候,Provider的value屬性會把初始化的資料覆蓋掉。

實踐

為了適應專案的需求,我主要是對ContextProvider和ContextConsumer做了封裝。

ContextProvider

由於在我的專案中元件需要訂閱並且修改和維護被髮布的資料,所以我需要有一個可以維護這些資料的地方。因此我建立了一個名為MyProvider的高階元件,並把它放在元件樹的頂層,而各個元件需要訂閱的資料就存放在MyProvider的state中,那麼我只需要維護它的state就能維護這些全域性的資料了。

export class MyProvider extends React.Component {
  constructor() {
    super();
    this.state = {
      name: "A",
      age: 18
    };
    this.updateContext = this.updateContext.bind(this);
  }
  updateContext(newData) {
    this.setState(Object.assign({}, this.state, newData));
  }
  render() {
    const contextData = { data: this.state };
    Object.defineProperty(contextData, "updateContext", {
      value: this.updateContext
    });
    return (
      <Context.Provider value={contextData}>
        {this.props.children}
      </Context.Provider>
    );
  }
}
複製程式碼

MyProvider元件返回的是ContextProvider元件,並把MyProvider的state作為要釋出的資料繫結到了ContextProvider的value屬性上。 前面講過,由於其他元件有要修改被髮布資料的需求,所以我給資料新增了一個不可修改的方法updateContext,這個方法能夠接收新的資料並更新MyProvider的state,即更新了被髮布的資料。 最後,MyProvider元件將自己的children原封不動的傳遞給ContextProvider。

ContextConsumer

考慮到ContextConsumer作為訂閱方使用比較頻繁,為了方便其他元件的使用,我將它封裝到高階元件中,並作為函式的返回值使用,如下:

export const MyConsumer = Component => {
  return props => (
    <Context.Consumer>
      {context => {
        return <Component context={context} {...props} />;
      }}
    </Context.Consumer>
  );
};
複製程式碼

MyConsumer函式返回一個高階元件。在這個高階元件中,我把ContextConsumer提供的資料加入到Component的props中,這樣我只需要在export元件的時候呼叫MyConsumer,並且在元件中使用this.props.context.data就能得到被髮布的資料了。如下:

class MyComponent extends React.Component {
  addAge() {
    const { data: { age }, updateContext } = this.props.context;
    const newAge = age + 1;
    updateContext({ age: newAge });
  }
  render() {
    const { name, age } = this.props.context.data;
    return (
      <div>
        name:
        <span>{name}</span>
        age:
        <span>{age}</span>
        <button onClick={() => this.addAge()}>add age</button>
      </div>
    );
  }
}

export default Consumer(MyComponent);
複製程式碼

在這個例子中,點選按鈕,呼叫this.props.context.updateContext方法就可以通過更新MyProvider的state來修改被髮布資料中的age的值。

示例程式碼:react-new-context-api-demo

小結

我折騰了兩天之後才反應過來,這不就是一個類似於redux的東西嗎?

可能由於我redux用的多了,對於Prvider和Consumer的封裝下意識的做成了類似redux的用法。再加上使用MyProvider的state作為唯一資料來源,又有updateContext這個有點像dispatch的方法來更新資料,乍一看之下,還是有點redux的影子的。

當然了,我自己寫的完全沒有redux那麼好用,也沒有reudx那麼嚴謹。所以,後來我又花了一個上午的時間改用了redux。

但是通過這次的實踐,也算是熟悉的new context api的用法,對redux也加深了了解吧。

最後,如果你只是想要訂閱資料,new context api是個不錯的選擇;但是如果你想要修改和維護被髮布的資料,使用redux會更方便和安全。

相關文章