# React Hook - useContext
###### tags: `Javascript, React`
> useContext 是一個傳遞 props 的方法,可以很方便的傳遞到需求的 component 而不需要路過其他地方(方向只能父傳子)
## Class component 中的 context API
先從 class API 複雜版本的介紹起
我們要做到的效果是:點擊按鈕切換 Class Theme 的顏色

### App.js
首先為了操作 context API
1. 輸出 ThemeContext = React.createContext() 並且在要使用的 component 內接收
2. 使用 ThemeContext.Provider 包裹住要被傳遞 context 的 component 們
只要以上兩個步驟就可以把 App 內的 useState 給其他 component 使用摟!
這邊注意在 Provider 裡面不管包了幾層,都只要引入 ThemeContext 就可以使用 parent component 的 props 摟
```javascript=
import './App.css';
import React,{useState} from 'react';
import ClassContextComponent from './ClassContextComponent'
export const ThemeContext = React.createContext();
export default function App() {
const [darkTheme,setDarkTheme] = useState(true);
// 這邊函式很簡單就是開關 darkTheme true/false 並且當作 props 傳下去
function toggleTheme (){
setDarkTheme(prevDarkTheme => !prevDarkTheme)
}
return (
<ThemeContext.Provider value={darkTheme}>
<button onClick={toggleTheme}>Toggle Theme</button>
<ClassContextComponent></ClassContextComponent>
</ThemeContext.Provider>
);
}
```
### ClassContextComponent.js
這邊要接收來自 parent component 的 props
1. 首先要引入 ThemeContext
2. 使用 Consumer 包裹住 JSX
如此一來就可以使用傳下來的 props darkTheme 摟!
```javascript=
import React,{Component} from 'react'
import {ThemeContext} from './App'
export default class ClassContextComponent extends Component {
themeStyles(dark){
return {
backgroundColor:dark? '#333' :'#ccc',
color:dark? '#ccc':'#333',
padding:'2rem',
margin:'2rem'
}
}
render(){
return(
<ThemeContext.Consumer>
{darkTheme =>{
return <div style={this.themeStyles(darkTheme)}> Class Theme</div>
}}
</ThemeContext.Consumer>
)
}
}
```
### 小結
使用 context API 的好處就是 props 傳遞上面非常方便,不需要傳遞過去經過的 component ,只需要引入 createContext 的變數即可使用
但是整個過程操作上是比較麻煩的,接下來我們來看看 funciont component 會如何操作 useContext hook 簡化整個過程
## Function component 中的 useContext
### FunctionContextComponent.js
是不是明顯簡潔超級多
不需要再使用 comsumer 來包裹著 JSX 了,直接使用 useContext 解構出 darkTheme ,再把 darkTheme 擺入 styles 裡面就完事了,超級好用
```javascript=
import React,{useContext} from 'react'
import {ThemeContext} from './App'
export default function FunctionContextComponent() {
const darkTheme = useContext(ThemeContext)
const themeStyles = {
backgroundColor:darkTheme? '#333' :'#ccc',
color:darkTheme? '#ccc':'#333',
padding:'2rem',
margin:'2rem'
}
return (
<div style={themeStyles}>Function Theme</div>
)
}
```
### App.js
這邊操作的方式跟 class component 的 parent component 操作方式是一樣的
1. 因為要操作 FunctionContextComponent 所以要記得引入
2. JSX 中也要記得引入
```javascript=
import './App.css';
import React,{useState} from 'react';
import FunctionContextComponent from './FunctionContextComponent'
import ClassContextComponent from './ClassContextComponent'
export const ThemeContext = React.createContext();
export default function App() {
const [darkTheme,setDarkTheme] = useState(true);
function toggleTheme (){
setDarkTheme(prevDarkTheme => !prevDarkTheme)
}
return (
<ThemeContext.Provider value={darkTheme}>
<button onClick={toggleTheme}>Toggle Theme</button>
<FunctionContextComponent></FunctionContextComponent>
<ClassContextComponent></ClassContextComponent>
</ThemeContext.Provider>
);
}
```
他們可以做到一樣的效果

## 運用 custom hook 繼續簡化程式碼
> 簡化程式碼的目的就是 Don't repeat yourself
首先建立一個新的檔案
### ThemeContext.js
我們一步步解釋這邊做了什麼
1. 我想把 ThemeContext.Provider 拉出來
把 darkTheme 內容拉進此資料夾並且使用 `createContext()` 為了可以從這個檔案輸出給 children component
2. 因為我想把按鈕以及裡面的函式(toggleTheme)拉出來
把 toggleTheme 函式拉近來這個檔案並且並且使用 `createContext()` 為了可以從這個檔案輸出給 children component
3. 建立兩個 costom hook 來返回 useContext 這樣就可以把這些重複的程式碼都寫在這個 ThemeContex.js 內,並且做 export 就可以在其他 component 內使用摟
5. 在 JSX 使用上全部的 Provider 並且填入 value(就是要傳進去的 props)包裹著 children
這邊的 children 代表裡面包裹著的 component
7. 把整個 ThemeProvider 輸出到 App.js 就可以大大簡化其內容了
```javascript=
import React,{useState,useContext} from 'react';
const ThemeContext = React.createContext();
const ThemeUpdateContext = React.createContext();
export function useTheme(){
return useContext(ThemeContext)
}
export function useThemeUpdate(){
return useContext(ThemeUpdateContext)
}
export function ThemeProvider ({children}){
// 下面的 useState 以及 toggle 函式,是因為要簡化 App.js 內容被拉進來
const [darkTheme,setDarkTheme] = useState(true);
function toggleTheme (){
setDarkTheme(prevDarkTheme => !prevDarkTheme)
}
return (
<ThemeContext.Provider value={darkTheme}>
<ThemeUpdateContext.Provider value={toggleTheme}>
{children}
</ThemeUpdateContext.Provider>
</ThemeContext.Provider>
)
}
```
### App.js
這簡潔的程式碼是否看了就開心?
剛剛那一堆程式碼都被擷取成短短個 ThemeProvider tag 就完事了
```javascript=
import React from 'react';
import FunctionContextComponent from './FunctionContextComponent'
import {ThemeProvider} from './ThemeContext';
export default function App() {
return (
<ThemeProvider>
<FunctionContextComponent></FunctionContextComponent>
</ThemeProvider>
);
}
```
### FunctionContextComponent.js
接下來看看這邊要怎麼使用調整過後的 useContext,以及讓裝進去的按鈕跑得動
1. 首先 useContext 因為我整個拉到 ThemeContext.js 內操作 cumstom hook 了所以這邊就引入他們即可
操作方式也很簡單直接解構出其中的 value 就可以直接使用,也就是 darkTheme, toggleTheme 兩個
2. 接下來就把引進來的 props 放入去的位置即可,程式就可以正常跑摟!
```javascript=
import React from 'react'
import { useTheme,useThemeUpdate } from './ThemeContext'
export default function FunctionContextComponent() {
const darkTheme = useTheme();
const toggleTheme = useThemeUpdate();
const themeStyles = {
backgroundColor:darkTheme? '#333' :'#ccc',
color:darkTheme? '#ccc':'#333',
padding:'2rem',
margin:'2rem'
}
return (
<>
<button onClick={toggleTheme}>Toggle Theme</button>
<div style={themeStyles}>Function Theme</div>
</>
)
}
```