iT邦幫忙

2021 iThome 鐵人賽

DAY 9
0
Modern Web

React 從 0.5 到 1系列 第 9

[鐵人賽 Day09] React Context(上)-單純的用法

Why Context

在寫 React 網站的時候,因為資料都是由上而下的藉由元件傳遞,有些情況,例如:整體 UI 主題、使用者身份驗證、偏好語言...等廣泛應用的資料,就會顯得太笨重。同樣的資料要同時傳遞給很多下層元件、而且這些元件可能都在深處,會讓 code 更加凌亂且難以閱讀。對此,React 提供了一種方法:Context,讓資料的分享可以不需要如此繁複。

How To Use Context

Context 的設計理念是用來分享那些在元件樹中,稱得上 global 的資訊。例如下面的情境,就是當使用者,點選了「黑夜版」的網頁:


// 使用 createContext 方法,為目前的網站色調建立 context,並給予一個預設值  
const ThemeContext = React.createContext('morning');

class App extends React.Component {
  render() {
		// 使用 Provider 方法,把上面建立的 context 傳遞給底下的元件,這裏重新賦予新的黑夜版樣式 
		// 不論元件埋的多深,都可以讀到
    return (
      <ThemeContext.Provider value="night">
        <Menu />
				....裡面的 component 都可以收到
      </ThemeContext.Provider>
    );
  }
}

// Menu 作為傳遞的中間人,不需要有把 props 往下傳的行為
function Menu() {
  return (
    <div>
      <MenuButton />
			<MenuButton />
			<MenuButton />
    </div>
  );
}

class MenuButton extends React.Component {
	// 要閱讀到我們一開始定義的 ThemeContext,需要把它賦值給 contextTyp
	// 這裡的運作是這樣子:React 會去找到最接近且吻合的 theme provider 並使用它的值(也就是'night')
  static contextType = ThemeContext;
  render() {
    return <Button color={this.context} />;
  }
}

你要傳遞下去的資訊,也有可能是一大包,收攏在另外的文件裡,如以下的案例,此時,可以這樣傳遞:

///// theme 的定義區塊	theme.js
export const themes = {
  morning: {
    fontColor: 'black',
    background: 'light-grey',
		q
  },
  night: {
    fontColor: 'white',
    background: 'blue',
  },
};

export const ThemeContext = React.createContext(
  themes.night
);

///// theme 的使用者 footer.js

import {ThemeContext} from './theme-context';

class ThemeFooter extends React.Component {
  render() {
    let props = this.props;
    let theme = this.context;
    return (
      <Footer
        {...props}
        style={{backgroundColor: theme.background}}
      />
    );
  }
}
// 這裏把 import 進來的樣式表,塞進元件的 contextType 裡
ThemeFooter.contextType = ThemeContext;
export default ThemeFooter;

///// theme 的切換區塊	app.js
import {ThemeContext, themes} from './theme';
import ThemeFooter from './footer';

class App extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      theme: themes.morning,
    };
  }

  render() {
    return (
      <Page>
        <ThemeContext.Provider value={this.state.theme}>
					</ThemeFooter>
					//....其他需要使用樣式的元件
        </ThemeContext.Provider>
      </Page>
    );
  }
}

ReactDOM.render(<App />, document.root);

什麼情況下不該用 Context ?

過度的使用 context,會讓你的元件變得不好複用,如果你單純只是討厭把資料傳遞很多層(而非那些廣泛運用的情境),可以考慮別的作法。以下舉一個例子:假設網站的 照片、使用者名稱、性別... 在最上層取得,需要經過頁面 layout、Header、Menu 區域...才能抵達最後會用到資訊的 使用者頭像 元件,除了層層傳遞或者用 context(不建議),還可以如何解決?

解決方法之一是,不要單獨傳遞 props 資訊下去,你可以直接傳遞一整個使用者頭像元件,中間用來傳遞的元件,就不需要看到那些細碎的使用者資料。

// 這裡是最上層取得使用者資訊的地方
function Page(props){
		// 把使用者頭像在上層,連同要用到的資訊打包好
  const userAvatar = <Avatar user={user} size={props.avatarSize} />;
	// 再把 使用者頭像元件 當成 props 傳遞下去
  return <PageLayout userAvatar={userAvatar} />;
}

然而,這樣的做法也並非適用於每個案例,當我們把 userAvatar 在上層打包好,也意味著會增加上層元件的複雜程度。

這樣子的 props 並不侷限於只能應用一組資料,你也可以為要傳遞下去的 props 建立很多個不同的「插槽」(如範例)。如果你傳遞下去的這些元件,有與上層元件溝通的需求,你可以考慮 Render Props 的方法。

function Page(props){
  const blockContent = <Description user={user} />;
  const header = <Avatar user={user} size={props.avatarSize} />;
	const detail = <Detail infor={props.infor} />;
  return (
    <PageLayout
      header={header}
      content={blockContent}
			moreInfor={detail}
    />
  );
}

以上的案例情境都較為單純,但實際在使用 context 的時候,情況可能更加複雜,有時候會有多組的 context 需要傳遞,或者 theme toggle 也需要一併傳遞到子元件的情況...,請見下篇文章。


上一篇
[鐵人賽 Day08] 如何使用 memoization 方法減少 useContext 非必要 re-render 的效能問題?
下一篇
[鐵人賽 Day10] Context(下)-花式用法
系列文
React 從 0.5 到 115
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言