Chia sẻ state giữa các component là bài toán phổ biến trong React app. Giải pháp tốt nhất là bạn phải lifting state. Nhưng nó yêu cầu bạn phải truyền props xuống cho các component con. Điều đó không phải là vấn đề quá lơn, nhưng trong một số trường hợp truyền props xuống quá nhiều cấp component lại rất khó khăn và tốn nhiều thời gian.
Để tránh được vấn đề này, bạn có thể tạo một context để thêm những shared state và sau đó component nào cần thì bạn gọi context đó ra xài, không cần phải truyền từ cha xuống con nữa. Nó cũng giống như biến global được chia sẻ và sử dụng ở nhiều nơi, nhưng context được quản lí tốt hơn để maintain code dễ hơn.
Ví dụ sử dụng context như sau:
import * as React from 'react'
const FooContext = React.createContext()
function FooDisplay() {
const foo = React.useContext(FooContext)
return <div>Foo is: {foo}</div>
}
ReactDOM.render(
<FooContext.Provider value="I am foo">
<FooDisplay />
</FooContext.Provider>,
document.getElementById('root'),
)
Ở những phiên bản trước của React Context, bạn có thể tham khảo bài viết tại đây.
<FooDisplay />
có thể nằm ở bất cứ đâu trong App tree của bạn, nó đều có thể truy xuất giá trị foo bằng cú pháp React.useContext. Bạn có thể truyền giá trị mặc định cho context bằng tham số thứ hai React.createContext(‘default value’) nhưng tôi ít khi sử dụng giá trị default cho context, bởi vì chúng ta có thể quên wrap context Provider cho component của chúng ta, ở phần sau tôi sẽ chỉ cho bạn cách check để chúng ta không quên wrap Provider cho component.
Sử dụng context với Counter
Bạn hãy sử dụng context để tạo một Counter component như sau:
import * as React from 'react'
// 🐨 tạo CountContext với React.createContext
function CountDisplay() {
// 🐨 lấy count từ useContext và CountContext
const count = 0
return <div>{`The current count is ${count}`}</div>
}
function Counter() {
// 🐨 lấy setCount từ useContext và CountContext
const setCount = () => {}
const increment = () => setCount(c => c + 1)
return <button onClick={increment}>Increment count</button>
}
function App() {
return (
<div>
{/*
🐨 bọc hai component này vào CountProvider để chúng có thể lấy giá trị từ context
*/}
<CountDisplay />
<Counter />
</div>
)
}
export default App
Đáp án:
import * as React from 'react'
const CountContext = React.createContext()
function CountProvider(props) {
const [count, setCount] = React.useState(0)
const value = [count, setCount]
// bạn cũng có thể viết như sau:
// const value = React.useState(0)
return <CountContext.Provider value={value} {...props} />
}
function CountDisplay() {
const [count] = React.useContext(CountContext)
return <div>{`The current count is ${count}`}</div>
}
function Counter() {
const [, setCount] = React.useContext(CountContext)
const increment = () => setCount(c => c + 1)
return <button onClick={increment}>Increment count</button>
}
function App() {
return (
<div>
<CountProvider>
<CountDisplay />
<Counter />
</CountProvider>
</div>
)
}
export default App
Tạo một consumer hook
Bạn hãy tưởng tượng nếu có một component sử dụng counter context nhưng quên khai báo Provider thì chuyện gì sẽ xảy ra.
ReactDOM.render(<FooDisplay />, document.getElementById('root'))
Nếu bạn không truyền giá trị default cho context thì FooDisplay sẽ render: <div>Foo is: </div>.
Bởi vì giá trị của context sẽ là undefined.
Trong thực tế chúng ta cần ngăn chặn điều này, khi bạn quên wrap Provider sẽ dẫn tới những lỗi rất khó debug sau này. Cho nên bạn hãy tạo một consumer hook, được sử dụng như sau:
const [count, setCount] = useCount()
Và nếu bạn thay đổi App như sau:
function App() {
return (
<div>
<CountDisplay />
<Counter />
</div>
)
}
React sẽ thông báo lỗi cho bạn là bạn đang thiếu Provider cho React Context Counter và bạn phải wrap Provider lại bên ngoài <CountDisplay />
và <Counter />
Đáp án:
import * as React from 'react'
const CountContext = React.createContext()
function CountProvider(props) {
const [count, setCount] = React.useState(0)
const value = [count, setCount]
return <CountContext.Provider value={value} {...props} />
}
function useCount() {
const context = React.useContext(CountContext)
if (!context) {
throw new Error('useCount must be used within a CountProvider')
}
return context
}
function CountDisplay() {
const [count] = useCount()
return <div>{`The current count is ${count}`}</div>
}
function Counter() {
const [, setCount] = useCount()
const increment = () => setCount(c => c + 1)
return <button onClick={increment}>Increment count</button>
}
function App() {
return (
<div>
<CountProvider>
<CountDisplay />
<Counter />
</CountProvider>
</div>
)
}
export default App
thaunguyen.com via Epic React by Kent C.Dodds
cảm ơn a vì bài viết
hay anh ơi, ra tiếp đi anh
Thanks em. Chuẩn bị ra tiếp phần pattern nhe
hay quá anh ơi, rất thích series React của anh ạ
thanks em
ReactJS: Quản lý server state với react-query - Thau Nguyen
[…] có thể nằm bên ngoài hay bên trong cha của nó thì bạn có thể sử dụng Redux hay React.createContext để tạo global state, chia sẻ state sẽ dễ […]
Cảm ơn vì bài viết, dễ hiểu dễ follow 😀
thanks ban