Ở bài viết trước, chúng ta đã tìm hiểu về khái niệm Context, các API liên quan và cách sử dụng. Trong bài viết này, chúng ta sẽ tìm hiểu về cách sử dụng nhiều Context trong component tree và lịch sử thay đổi của nó qua các phiên bản React như thế nào nhé!
Consuming multiple Context là gì?
Giới thiệu chung
Trong một dự án, các component được tạo thành một cấu trúc dạng cây. Mỗi component đều quản lý và nhận các giá trị khác nhau. Vì vậy, sử dụng một Context Consumer/Provider cho tất cả sẽ ảnh hưởng đến việc re-render.
Để giữ context re-render nhanh chóng và hợp lý, ta cần tách các context theo từng nhóm component cha-con cần thiết.
Ví dụ minh hoạ
Xét ví dụ sau:
Tạo một 2 context: một context chứa tên giao diện, một context chứa tên người dùng
// Theme context
const ThemeContext = React.createContext("light");
// Signed-in user context
const UserContext = React.createContext({
name: "Danny",
});
Tạo một function component Question để hiển thị câu hỏi, sử dụng Consumer để lấy giá trị của ThemeContext:
const Question = () => (
<ThemeContext.Consumer>
{(theme) => <h1 className={theme}>We would like check your name :)</h1>}
</ThemeContext.Consumer>
);
Tạo một class component Button để hiển thị các nút, sử dụng static contextType để lấy context theme, và `this.context` để lấy giá trị của ThemeContext:
// Assign contextType to use
// Use `this.context`
class Button extends React.Component {
static contextType = ThemeContext;
render() {
return <button className={this.context}>{this.props.children}</button>;
}
}
Tạo một function component UserInfo, tương tự conponent Question nhưng ở đây ta sử dụng thêm một UserContext để thấy thông tin của user:
// Using Consumer Component in Pure Component
const UserInfo = () => (
<ThemeContext.Consumer>
{(theme) => (
<UserContext.Consumer>
{(user) => <h2 className={theme}>You are {user.name}?</h2>}
</UserContext.Consumer>
)}
</ThemeContext.Consumer>
);
Cuối cùng ta gọi tất cả vào component cha App, bọc bởi component Provider của ThemeContext rồi render bởi ReactDOM.render():
class App extends React.Component {
constructor(props) {
super(props);
this.state = { theme: "light" };
this.toggleTheme = this.toggleTheme.bind(this);
}
toggleTheme() {
this.setState({ theme: this.state.theme === "light" ? "dark" : "light" });
}
render() {
return (
<ThemeContext.Provider value={this.state.theme}>
<div className="app ">
<button onClick={this.toggleTheme}>Toggle Theme</button>
<Question />
<UserInfo />
<Button>Yes</Button>
<Button>No</Button>
</div>
</ThemeContext.Provider>
);
}
}
ReactDOM.render(<App />, document.getElementById("root"));
Như ta thấy, việc tách context làm chúng cũng sẽ không ảnh hưởng đến nhau, component cần giá trị nào thì chỉ cần gọi đến context đó, điều này sẽ giúp ta quản lý các dữ liệu tốt và dễ debug hơn.
Re-render Context (reference identity) là gì?
Context sử dụng một thuật ngữ là reference identity để nhận biết khi nào component được re-render.
Identity đơn giản là một thuộc tính duy nhất của đối tượng dùng để phân biệt với nhau. Vì thế, Reference identity là việc tham chiếu đến thuộc tính của đối tượng, cụ thể là tham chiếu đến vị trí của đối tượng trong bộ nhớ chứ không phải là giá trị thật sự.
Trong Context, có một số trường hợp đặc thù khi vô tình re-render component con trong một Consumer khi một Provider cha re-render, việc re-render ngoài ý muốn này nhiều khi sẽ phát sinh ra lỗi.
Ví dụ:
class App extends React.Component {
render() {
return (
<MyContext.Provider value={{something: 'something'}}>
<Toolbar />
</MyContext.Provider>
);
}
}
Đoạn code trên sẽ re-render tất cả Consumer mỗi lần Provider re-render, bởi vì thuộc tính value luôn được khởi tạo giá trị mới mỗi khi Provider re-render. Phương pháp tạo object trên tương tự như sau:
Để giải quyết vấn để này, ta chuyển giá trị của value
lên state của component cha chứa Provider đó như sau:
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
value: {something: 'something'},
};
}
render() {
return (
<MyContext.Provider value={this.state.value}>
<Toolbar />
</MyContext.Provider>
);
}
}
Một số thay đổi của Context sau React 16.3
Trước đây, React 15 sử dụng PropTypes để khởi tạo một context.
tim-hieu-khai-niem-context-phan-2
Ví dụ, chúng ta cần truyền giá trị color xuống component con Button và Message để styling. React 15 sử dụng context như sau:
class Button extends React.Component {
render() {
return (
<button style={{ background: this.context.color }}>
{this.props.children}
</button>
);
}
}
Button.contextTypes = {
color: PropTypes.string,
};
class Message extends React.Component {
render() {
return (
<div>
{this.props.text} <Button>Delete</Button>
</div>
);
}
}
class MessageList extends React.Component {
getChildContext() {
return { color: "purple" };
}
render() {
const children = this.props.messages.map((message) => (
<Message text={message.text} />
));
return <div>{children}</div>;
}
}
MessageList.childContextTypes = {
color: PropTypes.string,
};
class App extends React.Component {
render() {
return <MessageList messages={[{ text: "Context demo" }]} />;
}
}
Để lấy giá trị của context ở component cha truyền cho các component con, React 15 sử dụng childContextTypes và getChildContext. Bên cạnh đó component Button khởi tạo một contextTypes để truy cập đến giá trị của context. Nếu contextTypes không được khởi tạo thì giá trị của content sẽ là một object rỗng {}.
Nhưng từ React 16.3 trở đi, các API Context sẽ thay thế PropTypes.
- React.createContext(‘defaultValue’) sẽ thay thế cho contextType
- Các Provider/Consumer sẽ thay thế cho childContextType và getChildContext
Hy vọng bài viết trên sẽ giúp các bạn hiểu hơn về Context trong React. Cám ơn các bạn đã ghé thăm bài viết!
Bài viết có tham khảo thông tin từ link: https://reactjs.org/docs/context.html