Re: 新手让网页 act 起来: Day20 - React Hooks 之 useContext 与 createContext

前言

在之前 Lift state 的文章有提过,当我们有两个元件须共用到同一个 state 会将 state 提升然後再向下传给需要的元件。那如果说不只两个,可能有四、五个以上,那我们可能就会再提升,然後再透过 props 一层一层传递。除了透过 props 传递,其实我们还能用 context 来达到帮助我们将 state 传递下去,今天就让我们来介绍如何使用 React context 吧!

useContext 与 createContext

在使用 useContext 前必须先使用 createContext 来建立一个 Context object。它可以接收一个参数作为 defaultValue 。

const Context = createContext(defaultValue)

而这个 Context 物件,会有 Provider 属性,那它其实是 React element 所以我们可用 JSX 的形式呈现,并传入 value props 。

<Context.Provider value={/* some value */}>
  {children}
</Context.Provider>

而被 Context.Provider 所包含的元件,都可以取得 value。那究竟该怎麽取呢? 这个时候就是用到 useContext 啦!

使用 useContext 并将我们一开始使用 createContext 所回传的 Context object 传入就能够取得我们在 provider 提供的 value 了!

const value = useContext(Context)

以上就是 useContext 与 createContext 的搭配使用,接下来就让我们来看看范例,加深对 context 的印象吧!

范例

假设我们有个 App 结构如下

// App.jsx
import { Header } from './components/Header'
import { Body } from './components/Body'
import { Footer } from './components/Footer'

function App() {
  const [ darkTheme, setDarkTheme ] = React.useState(false)
  const theme = darkTheme ? 'dark' : 'light'
  
  return (
    <>
      <Header themeState={[theme, setDarkTheme]}/>
      <Body theme={theme}/>
      <Footer themeState={[theme, setDarkTheme]}/>
    </>
  )
}

ReactDOM.render(<App />, document.getElementById('root'));
// Header.jsx
import { ButtonTheme } from './ButtonTheme'

function Header({themeState}) {
  return (
    <div>
      <ButtonTheme themeState={themeState}/>
    </div>
  )
}
// Footer.jsx
import { ButtonTheme } from './ButtonTheme'

export function Footer({themeState}) {
  return (
    <div>
      <ButtonTheme themeState={themeState}/>
    </div>
  )
}=
// Body.jsx
export function Body({theme}) {
  return (
    <div>
      {theme}
    </div>
  )
}
// ButtonTheme.jsx
export function ButtonTheme({themeState}) {
  return (
    <div>
      <Button themeState={themeState} />
    </div>
  )
}

function Button({themeState}) {
  const [theme, setDarkTheme] = themeState
  return (
    <button onClick={() => { setDarkTheme(prev => !prev) }}>
      {theme}
    </button>
  )
}

范例中,我们希望按下 Header 或是 footer 的按钮都可以改变 theme 的值,这个时候第一种做法就是在 App 元件建立 useState 并传下去让 button 透过 onClick 去更改状态,那 Body 元件也能够同步更换 theme 。那我们就尝试使用 context 来试试看!

  1. 创建 ThemeContext 档案,并使用 createContext 建立 ThemeContext,同时宣告
// ThemeContext.js
const ThemeContext = React.createContext('light')
  1. 建立 ThemeContext.Provider 的元件,并且将我们的 state 提供给 value
export function ThemeContextProvider({ children }) {
  const [darkTheme, setDarkTheme] = React.useState(false)
  const theme = darkTheme ? 'dark' : 'light'
  
  return (
    <ThemeContext.Provider value={[theme, setDarkTheme]}>
      {children}
    </ThemeContext.Provider>
  )
}
  1. 建立 useThemeContext
export function useThemeContext() {
  const contextValue = React.useContext(ThemeContext)
  
  if (!contextValue) {
    throw new Error('you should wrap the component in theme context provider')
  }

  return contextValue
}
  1. 接下来将 Header Body 与 Footer 元件包在 ThemeContextProvider 中
// App.jsx
import { Header } from './components/Header'
import { Body } from './components/Body'
import { Footer } from './components/Footer'
import { ThemeContextProvider } from './components/ThemeContext'

function App() {
  return (
    <ThemeContextProvider>
      <Header />
      <Body />
      <Footer />
    </ThemeContextProvider>
  )
}

ReactDOM.render(<App />, document.getElementById('root'));
  1. 将 Header 、Body 与 Footer 的用不到的 props 移除
function Body() {
    
  return (
    <div>
      {theme}
    </div>
  )
}

function Header() {
  return (
    <div>
      <ButtonTheme />
    </div>
  )
}

function Footer() {
  return (
    <div>
      <ButtonTheme />
    </div>
  )
}

function ButtonTheme() {
  return (
    <div>
      <Button />
    </div>
  )
}
  1. 在 Body 与 Button 中使用 useThemeContext()
import { useThemeContext } from './components/ThemeContext'

function Body() {
  const [theme] = useThemeContext()
  
  return (
    <div>
      {theme}
    </div>
  )
}
import { useThemeContext } from './components/ThemeContext'

function Button() {
  const [theme, setDarkTheme] = useThemeContext()

  return (
    <button onClick={() => { setDarkTheme(prev => !prev) }}>{theme}</button>
  )
}

这样我们就成功地使用 context 改写啦!可以发先使用 context 的写法,我们就不需要透过 props 一层一层的传递,只要在使用的元件中使用 useContext 就可以拿到 value。

以上就是关於 useContext 与 createContext 的使用方式!有什麽问题都欢迎在下方留言告诉我~~

该文章同步发布於:我的部落格


<<:  2.4.11 Design System - Switches/Toggle

>>:  [Day27] Vue 3 - 方法

[Day3] 安全签章 - XOR加密(HashID)

API流程 I have A Nonce, I have A key, Uh It's time t...

Data layer implementation (1)

在上一篇,我们把 Ktor client 加到 Dagger 的 object graph 内。现在...

[Day30] 谁怕谁,再来啊!

呼 ~ 终於到了最後一天了,这三十天,说真的有点痛苦煎熬 XD 不过我们先来回顾这次铁人赛介绍了哪些...

[30] 30 天从 Swift 学会 Objective-C:30 天内那些我不懂的部分

这个 30 天我们理解了 C 语言与 Objective-C,然而我其实有不少事情是没有看懂的,铁人...

端点安全防护(对应:资通安全防护)

适用人员: 技术人员。 适用法规: 资通安全责任等级分级办法 技术面分类提要 网路架构 端点安全防护...