React Context

1、作用

Context 提供了一個(gè)無需為每層組件手動(dòng)添加 props,就能在組件樹間進(jìn)行數(shù)據(jù)傳遞的方法,實(shí)現(xiàn)跨級(jí)父子組件間的通信。
在一個(gè)典型的 React 應(yīng)用中,數(shù)據(jù)是通過 props 屬性自上而下(由父及子)進(jìn)行傳遞的,但這種做法對(duì)于某些類型的屬性而言是極其繁瑣的(例如:地區(qū)偏好,UI 主題),這些屬性是應(yīng)用程序中許多組件都需要的。Context 提供了一種在組件之間共享此類值的方式,而不必顯式地通過組件樹的逐層傳遞 props。

2、使用 Context

(1)React.createContext(defaultValue)
const MyContext = React.createContext(defaultValue);

創(chuàng)建一個(gè) Context 對(duì)象,defaultValue為設(shè)置的默認(rèn)值。
當(dāng) React 渲染一個(gè)訂閱了這個(gè) Context 對(duì)象的組件,這個(gè)組件會(huì)從組件樹中離自身最近的那個(gè)匹配的 Provider 中讀取到當(dāng)前的 context 值。

(2)Context.Provider。
<MyContext.Provider value={/* 某個(gè)值 */}>

每個(gè) Context 對(duì)象都會(huì)返回一個(gè) Provider React 組件,它允許消費(fèi)組件訂閱 context 的變化。Provider 接收一個(gè) value 屬性,傳遞給消費(fèi)組件。一個(gè) Provider 可以和多個(gè)消費(fèi)組件有對(duì)應(yīng)關(guān)系。多個(gè) Provider 也可以嵌套使用,里層的會(huì)覆蓋外層的數(shù)據(jù)。
當(dāng) Provider 的 value 值發(fā)生變化時(shí),它內(nèi)部的所有消費(fèi)組件都會(huì)重新渲染。Provider 及其內(nèi)部消費(fèi)組件都不受制于 shouldComponentUpdate 函數(shù),因此當(dāng)消費(fèi)組件在其祖先組件退出更新的情況下也能更新。

(3) Class.contextType
class MyClass extends React.Component {
  componentDidMount() {
    let value = this.context;
    /* 在組件掛載完成后,使用 MyContext 組件的值來執(zhí)行一些有副作用的操作 */
  }
  componentDidUpdate() {
    let value = this.context;
    /* ... */
  }
  componentWillUnmount() {
    let value = this.context;
    /* ... */
  }
  render() {
    let value = this.context;
    /* 基于 MyContext 組件的值進(jìn)行渲染 */
  }
}
MyClass.contextType = MyContext;

掛載在 class 上的 contextType 屬性會(huì)被重賦值為一個(gè)由 React.createContext()創(chuàng)建的 Context 對(duì)象。這能讓你使用 this.context 來獲取最近 Context 上的那個(gè)值。你可以在任何生命周期中訪問到它,包括 render 函數(shù)中。

上面的概念看不懂,沒關(guān)系,下面我們通過擼代碼,一目了然~~~

 <script type="text/babel">
      /*1、 創(chuàng)建一個(gè) context({id:00000,name:XX,address:'China'}為默認(rèn)值)。*/
      const DataContext = React.createContext({id:'00000',name:'XX',address:'China'});
      class App extends React.Component {
        constructor(props) {
          super(props);
          this.id = 1000;
          this.state = {
            obj: {
              id: this.id,
              name: "CaiKe",
              address: "上海徐匯區(qū)"
            }
          };
        }
        render() {
          setTimeout(() => {
            this.setState({
              obj: {
                id: ++this.id,
                name: "Kevin",
                address: "上海浦東新區(qū)"
              }
            });
          }, 1500);
          /* 使用一個(gè) Provider 來將當(dāng)前的 DataContext 傳遞給以下的組件樹。
          * 無論多深,任何組件只要獲取Context,都能讀取這個(gè)值。
          *在這個(gè)例子中,我們將this.state.obj作為當(dāng)前的值傳遞下去。
          */
          return (
            <DataContext.Provider value={JSON.stringify(this.state.obj)}>
              <MiddleCom />
            </DataContext.Provider>
          );
        }
      }

      // 中間的組件再也不必指明往下傳遞 DataContext 了。
      function MiddleCom(props) {
        return (
          <div>
            <ChildCom />
          </div>
        );
      }

      class ChildCom extends React.Component {
        // 3、指定 contextType 讀取當(dāng)前的 DataContext。
        // React 會(huì)往上找到最近的 Provider,然后使用它的值。
        // 在這個(gè)例子中,當(dāng)前的 DataContext 值為 App 組件中的state的obj的值。
       static contextType = DataContext;
        constructor(props) {
          super(props);
          this.value = null;
        }
        componentDidMount() {
       // 4、使用 this.context 來獲取最近 Context 上的那個(gè)值,及在Provider 上設(shè)置的value
          this.value = this.context;
        }
        componentDidUpdate(prevProps, prevState) {
          this.value = this.context;
        }
        render() {
          return <div>{this.value}</div>;
        }
      }
      // 也可以這樣設(shè)置組件的 contextType 屬性,賦值為一個(gè)由 React.createContext()創(chuàng)建的 Context 對(duì)象
      // ChildCom .contextType = DataContext;
      ReactDOM.render(<App />, document.getElementById("root"));
    </script>
效果圖

上面代碼中,創(chuàng)建了一個(gè)DataContext(第一步),設(shè)置默認(rèn)值為{id:'00000',name:'XX',address:'China'};使用DataContext.Provider包裹子組件(第二步),設(shè)置value為App組件的state.obj;最后指定 contextType 讀取當(dāng)前的DataContext(第三步),通過this.context獲取到了在父組件的父組件中設(shè)置的數(shù)據(jù)。
基本大功告成。o(∩_∩)o 哈哈~~
我們?cè)贏pp組件中定時(shí)修改數(shù)據(jù),作為中間組件的MiddleCom,沒有做任何傳遞,ChildCom組件正確的顯示出了數(shù)據(jù)。

(4)Context.Consumer
<MyContext.Consumer>
  {value => /* 基于 context 值進(jìn)行渲染*/}
</MyContext.Consumer>

組件訂閱到 context 變更,在函數(shù)式組件中完成訂閱 context。
需要使用函數(shù)做為子元素。這個(gè)函數(shù)接收當(dāng)前的 context 值,返回一個(gè) React 節(jié)點(diǎn)。傳遞給函數(shù)的 value 值等同于往上組件樹離這個(gè) context 最近的 Provider 提供的 value 值。如果沒有對(duì)應(yīng)的 Provider,value 參數(shù)等同于傳遞給 createContext() 的 defaultValue。

  // 確保傳遞給 createContext 的默認(rèn)值數(shù)據(jù)結(jié)構(gòu)是調(diào)用的組件(consumers)所能匹配的!
      const DataContext = React.createContext({
        id: "1234",
        name: "張三"
      });
      function ShowText() {
        //訂閱context變更
        return (
          <DataContext.Consumer>
            {({ id, name }) => (
              <div>
                <div>{id}</div>
                <div>{name}</div>
              </div>
            )}
          </DataContext.Consumer>
        );
      }
      class App extends React.Component {
        constructor(props) {
          super(props);
          this.state = {
            id: 1001,
            name: "Zhang"
          };
        }
        componentDidMount() {
          setInterval(() => {
            this.setState((state, props) => ({
              id: ++state.id,
              name: props.name + "_" + state.id
            }));
          }, 1000);
        }

        render() {
          // 整個(gè) state 都被傳遞進(jìn) provider
          return (
            <DataContext.Provider value={this.state}>
              <ContentOne />
            </DataContext.Provider>
          );
        }
      }
      //中間組件1
      function ContentOne() {
        return (
          <div>
            <ContentTwo />
          </div>
        );
      }
      //中間組件2
      function ContentTwo() {
        return (
          <div>
            <ShowText />
          </div>
        );
      }
      ReactDOM.render(<App name="張三" />, document.getElementById("root"));

3、注意點(diǎn)

Context 主要應(yīng)用場(chǎng)景在于很多不同層級(jí)的組件需要訪問同樣一些的數(shù)據(jù)。請(qǐng)謹(jǐn)慎使用,因?yàn)檫@會(huì)使得組件的復(fù)用性變差。

如果你只是想避免層層傳遞一些屬性,組件組合(component composition)有時(shí)候是一個(gè)比 context 更好的解決方案。

另外一種無需 context 的解決方案是將深層次的組件自身傳遞下去,通過props來獲取,因而中間組件無需知道這些自己不需要的props。

function Page(props) {
  const user = props.user;
  const content = <Feed user={user} />;
  const topBar = (
    <NavigationBar>
      <Link href={user.permalink}>
        <Avatar user={user} size={props.avatarSize} />
      </Link>
    </NavigationBar>
  );
  return (
    <PageLayout
      topBar={topBar}
      content={content}
    />
  );
}

這種對(duì)組件的控制反轉(zhuǎn)減少了在你的應(yīng)用中要傳遞的 props 數(shù)量,這在很多場(chǎng)景下會(huì)使得你的代碼更加干凈,使你對(duì)根組件有更多的把控。但是,這并不適用于每一個(gè)場(chǎng)景:這種將邏輯提升到組件樹的更高層次來處理,會(huì)使得這些高層組件變得更復(fù)雜,并且會(huì)強(qiáng)行將低層組件適應(yīng)這樣的形式,這可能不會(huì)是你想要的。
這種模式足夠覆蓋很多場(chǎng)景了,在這些場(chǎng)景下你需要將子組件和直接關(guān)聯(lián)的父組件解耦。如果子組件需要在渲染前和父組件進(jìn)行一些交流,你可以進(jìn)一步使用 render props。

4、總結(jié)

有的時(shí)候在組件樹中很多不同層級(jí)的組件需要訪問同樣的一批數(shù)據(jù)。Context 能讓你將這些數(shù)據(jù)向組件樹下所有的組件進(jìn)行“廣播”,所有的組件都能訪問到這些數(shù)據(jù),也能訪問到后續(xù)的數(shù)據(jù)更新。使用 context 的通用的場(chǎng)景包括管理當(dāng)前的 locale,theme,或者一些緩存數(shù)據(jù),這比替代方案要簡(jiǎn)單的多。
還有很多注意點(diǎn)和細(xì)節(jié)問題,可以去官網(wǎng)Context學(xué)習(xí)。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容