useContext
const value = useContext(MyContext);
React.createContext
를 통해 생성된 context 객체를 받아 context의 현재 값을 반환한다.
- 가장 가까운
<MyContext.Provider>
가 갱신되면 가장 최신의 context value
를 사용해 렌더러를 트리거한다. 상위에서 React.memo
혹은 shouldComponentUpdate
를 사용하더라도 useContext
를 사용하고 있는 컴포넌트 자체에서부터 다시 렌더링된다.
- Context API는 상태를 전역적으로 공유해주는 기능만을 수행한다.
value
가 객체로 되어있을 경우 하나라도 바뀌면 전체가 리렌더링된다.
- context 값의 변화는 Object.is()와 동일한 알고리즘으로 비교한다.
useContext 원리
function createEventEmitter(value) {
let handlers = []
return {
on(handler) {
handlers.push(handler)
},
off(handler) {
handlers = handlers.filter(h => h !== handler)
},
get() {
return value
},
set(newValue) {
value = newValue
handlers.forEach(handler => handler(value))
},
}
}
- 이벤트를 발행한뒤 이를 구독하고 있는 객체에게 통지하는 역할을 이벤트 에미터로 구현할 수 있다.
- 이 경우에는 이벤트 에미터가 갖고 있을 메시지, 즉 value를 인자로 받고 on을 통해 handlers 배열에 구독자를 등록한다. get, set 메소드를 통해 메시지를 조회하고 새로운 메시지를 받아 구독자들에게 통지할 수 있다.
const MyReact = (() => {
function createContext(initialValue) {
const emitter = createEventEmitter(initialValue)
const Provider = ({ value, children }) => {
// value 값이 변하면 이벤트 에미터에게 이를 알린다.
// 이벤트 에미터는 구독하고 있는 객체들에게 이를 전파할 것이다.
React.useEffect(() => {
emitter.set(value)
}, [value])
return <>{children}</>
}
const Consumer = ({ children }) => {
// 리렌더링을 위해 이벤트 에미터의 값을 상태 value로 가지고 있다.
const [value, setValue] = useState(emitter.get())
// 이벤트 에미터를 구독한다.
// 이벤트 에미터가 변경을 알리면 이 값을 상태로 세팅한다.
// 상태가 변경되면 리액트는 이 컴포넌트를 다시 그릴 것이다.
React.useEffect(() => {
emitter.on(setValue)
return () => emitter.off(setValue)
}, [])
return <>{children(value)}</>
}
return {
Provider,
Consumer,
emitter,
}
}
// 컨택스트 값을 사용할 수 있는 훅이다.
function useContext(context) {
// 컨택스트 값을 상태 value로 저장해 둔다
const [value, setValue] = useState(context.emitter.get())
React.useEffect(() => {
// 컨택스트의 이벤트 에미터로부터 값을 수신하면 상태 value를 갱신다.
// 이 상태를 사용하는 컴포넌트는 리렌더링 될 것이다.
context.emitter.on(setValue)
return () => context.emitter.off(setValue)
}, [context])
// 컨택스트 값을 반환한다.
return value
}
return {
createContext,
// 훅을 제공한다.
useContext,
}
})()
const PlusButton = () => {
return (
<countContext.Consumer>
{({ count, setCount }) => (
<button onClick={() => setCount(count + 1)}>+ 카운트 올리기</button>)}
</countContext.Consumer>)
}