Context
Chúng ta hiểu Context
là gì, là nơi cung cấp cho chúng ta cách để truyền các giá trị xuống component con mà không cần truyền vào props của component con.
Thông thường khi truyền data giữa các Component thì chúng ta thường đưa chúng vào props của các component, điều này sẽ dẫn tới một số component sẽ có props rất là cồng kềnh và nhiều component có props giống nhau và để giải quyết vấn đề này thì chúng ta có một cách đó chính là sử dụng Context
When to Use Context
Lý thuyết là vậy nhưng chúng ta không sử dụng Context để truyền tất cả data được. Vậy chúng ta sẽ sử dụng Context để truyền những data như thế nào.
Context được thiết kế để chia sẻ data giữa các component, loại data được share thông qua Context thường là các loại mang tính chất “Global” dùng chung cho nhiều Component, ví dụ như: thông tin người dùng, theme, ngôn ngữ.
Chúng ta sẽ bắt đầu bằng một ví dụ với theme.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 | <span class="token keyword">class</span> <span class="token class-name">App</span> <span class="token keyword">extends</span> <span class="token class-name">React<span class="token punctuation">.</span>Component</span> <span class="token punctuation">{</span> <span class="token function">render</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">return</span> <span class="token operator"><</span>Toolbar theme<span class="token operator">=</span><span class="token string">"dark"</span> <span class="token operator">/</span><span class="token operator">></span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> <span class="token keyword">function</span> <span class="token function">Toolbar</span><span class="token punctuation">(</span>props<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token comment">// The Toolbar component must take an extra "theme" prop</span> <span class="token comment">// and pass it to the ThemedButton. This can become painful</span> <span class="token comment">// if every single button in the app needs to know the theme</span> <span class="token comment">// because it would have to be passed through all components.</span> <span class="token keyword">return</span> <span class="token punctuation">(</span> <span class="token operator"><</span>div<span class="token operator">></span> <span class="token operator"><</span>ThemedButton theme<span class="token operator">=</span><span class="token punctuation">{</span>props<span class="token punctuation">.</span>theme<span class="token punctuation">}</span> <span class="token operator">/</span><span class="token operator">></span> <span class="token operator"><</span><span class="token operator">/</span>div<span class="token operator">></span> <span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">class</span> <span class="token class-name">ThemedButton</span> <span class="token keyword">extends</span> <span class="token class-name">React<span class="token punctuation">.</span>Component</span> <span class="token punctuation">{</span> <span class="token function">render</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">return</span> <span class="token operator"><</span>Button theme<span class="token operator">=</span><span class="token punctuation">{</span><span class="token keyword">this</span><span class="token punctuation">.</span>props<span class="token punctuation">.</span>theme<span class="token punctuation">}</span> <span class="token operator">/</span><span class="token operator">></span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> |
Với ví dụ trên thì để sử dụng theme thì chúng ta sẽ phải đưa theme vào props của component và các bạn thử tưởng tượng xem nếu toàn bộ app sử dụng như vậy thì theme={props.theme}
sẽ bị lặp bao nhiêu lần.
Còn với Context chúng ta sẽ sử dụng như sau:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 | <span class="token comment">// Context lets us pass a value deep into the component tree</span> <span class="token comment">// without explicitly threading it through every component.</span> <span class="token comment">// Create a context for the current theme (with "light" as the default).</span> <span class="token keyword">const</span> ThemeContext <span class="token operator">=</span> React<span class="token punctuation">.</span><span class="token function">createContext</span><span class="token punctuation">(</span><span class="token string">'light'</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">class</span> <span class="token class-name">App</span> <span class="token keyword">extends</span> <span class="token class-name">React<span class="token punctuation">.</span>Component</span> <span class="token punctuation">{</span> <span class="token function">render</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token comment">// Use a Provider to pass the current theme to the tree below.</span> <span class="token comment">// Any component can read it, no matter how deep it is.</span> <span class="token comment">// In this example, we're passing "dark" as the current value.</span> <span class="token keyword">return</span> <span class="token punctuation">(</span> <span class="token operator"><</span>ThemeContext<span class="token punctuation">.</span>Provider value<span class="token operator">=</span><span class="token string">"dark"</span><span class="token operator">></span> <span class="token operator"><</span>Toolbar <span class="token operator">/</span><span class="token operator">></span> <span class="token operator"><</span><span class="token operator">/</span>ThemeContext<span class="token punctuation">.</span>Provider<span class="token operator">></span> <span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> <span class="token comment">// A component in the middle doesn't have to</span> <span class="token comment">// pass the theme down explicitly anymore.</span> <span class="token keyword">function</span> <span class="token function">Toolbar</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">return</span> <span class="token punctuation">(</span> <span class="token operator"><</span>div<span class="token operator">></span> <span class="token operator"><</span>ThemedButton <span class="token operator">/</span><span class="token operator">></span> <span class="token operator"><</span><span class="token operator">/</span>div<span class="token operator">></span> <span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">class</span> <span class="token class-name">ThemedButton</span> <span class="token keyword">extends</span> <span class="token class-name">React<span class="token punctuation">.</span>Component</span> <span class="token punctuation">{</span> <span class="token comment">// Assign a contextType to read the current theme context.</span> <span class="token comment">// React will find the closest theme Provider above and use its value.</span> <span class="token comment">// In this example, the current theme is "dark".</span> <span class="token keyword">static</span> contextType <span class="token operator">=</span> ThemeContext<span class="token punctuation">;</span> <span class="token function">render</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">return</span> <span class="token operator"><</span>Button theme<span class="token operator">=</span><span class="token punctuation">{</span><span class="token keyword">this</span><span class="token punctuation">.</span>context<span class="token punctuation">}</span> <span class="token operator">/</span><span class="token operator">></span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> |
Before You Use Context
Context thường là được sử dụng khi một số data cần được truy cập ở nhiều nhiều Component và ở nhiều cấp độ, Nhưng chúng ta cần phải thận trọng và không nên lạm dụng Context bởi vì nó sẽ làm cho việc tái sử dụng component trở nên khó hơn.
Nếu bạn chỉ muốn giảm tải việc truyền nhiều props giữa các component thì bạn nên xem xét sử dụng component composition hơn là Context.
Một ví dụ đơn giản như sau:
1 2 3 4 5 6 7 8 9 10 | <span class="token operator"><</span>Page user<span class="token operator">=</span><span class="token punctuation">{</span>user<span class="token punctuation">}</span> avatarSize<span class="token operator">=</span><span class="token punctuation">{</span>avatarSize<span class="token punctuation">}</span> <span class="token operator">/</span><span class="token operator">></span> <span class="token comment">// ... which renders ...</span> <span class="token operator"><</span>PageLayout user<span class="token operator">=</span><span class="token punctuation">{</span>user<span class="token punctuation">}</span> avatarSize<span class="token operator">=</span><span class="token punctuation">{</span>avatarSize<span class="token punctuation">}</span> <span class="token operator">/</span><span class="token operator">></span> <span class="token comment">// ... which renders ...</span> <span class="token operator"><</span>NavigationBar user<span class="token operator">=</span><span class="token punctuation">{</span>user<span class="token punctuation">}</span> avatarSize<span class="token operator">=</span><span class="token punctuation">{</span>avatarSize<span class="token punctuation">}</span> <span class="token operator">/</span><span class="token operator">></span> <span class="token comment">// ... which renders ...</span> <span class="token operator"><</span>Link href<span class="token operator">=</span><span class="token punctuation">{</span>user<span class="token punctuation">.</span>permalink<span class="token punctuation">}</span><span class="token operator">></span> <span class="token operator"><</span>Avatar user<span class="token operator">=</span><span class="token punctuation">{</span>user<span class="token punctuation">}</span> size<span class="token operator">=</span><span class="token punctuation">{</span>avatarSize<span class="token punctuation">}</span> <span class="token operator">/</span><span class="token operator">></span> <span class="token operator"><</span><span class="token operator">/</span>Link<span class="token operator">></span> |
Với ví dụ trên thì bạn cần phải truyền user
and avatarSize
xuống tới lớp cuối cùng và chỉ có lớp cuối cùng sử dụng 2 prop trên, nhưng ở ví dụ trên thì 2 props đó được truyền qua nhiều lớp Component và nó là dư thừa, và khi Avata
Component cần thêm một số props nữa thì chuyện gì sẽ xảy ra, props của các Component khác sẽ phình lên rất nhiều trong khi chúng ko có nhu cầu sử dụng các props đó.
Như đã nói ở trên thì chúng ta có thể vận dụng component composition bằng cách đưa component Avatar
lên đầu tiên và truyền nó qua các Component khác, như vậy các Component khác sẽ ko cần quan tâm rằng Avatar
sẽ thay đổi như thế nào mà nó chỉ cần nhận được 1 Component Avatar
là đủ, còn về phần Avatar
dù nó có cần bao nhiêu props đi nữa thì cũng ko ảnh hưởng tới các Component khác.
Với cách đảo ngược như này sẽ giúp code của bạn sạch sẽ hơn và dễ dàng maintenance
hơn nhiều bằng cách giảm tải tối đa việc truyền props, tuy nhiên nó không phải là cách tốt trong nhiều trường hợp, chuyển qua một ví dụ có độ phức tạp cao hơn một tí
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | <span class="token keyword">function</span> <span class="token function">Page</span><span class="token punctuation">(</span>props<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">const</span> user <span class="token operator">=</span> props<span class="token punctuation">.</span>user<span class="token punctuation">;</span> <span class="token keyword">const</span> content <span class="token operator">=</span> <span class="token operator"><</span>Feed user<span class="token operator">=</span><span class="token punctuation">{</span>user<span class="token punctuation">}</span> <span class="token operator">/</span><span class="token operator">></span><span class="token punctuation">;</span> <span class="token keyword">const</span> topBar <span class="token operator">=</span> <span class="token punctuation">(</span> <span class="token operator"><</span>NavigationBar<span class="token operator">></span> <span class="token operator"><</span>Link href<span class="token operator">=</span><span class="token punctuation">{</span>user<span class="token punctuation">.</span>permalink<span class="token punctuation">}</span><span class="token operator">></span> <span class="token operator"><</span>Avatar user<span class="token operator">=</span><span class="token punctuation">{</span>user<span class="token punctuation">}</span> size<span class="token operator">=</span><span class="token punctuation">{</span>props<span class="token punctuation">.</span>avatarSize<span class="token punctuation">}</span> <span class="token operator">/</span><span class="token operator">></span> <span class="token operator"><</span><span class="token operator">/</span>Link<span class="token operator">></span> <span class="token operator"><</span><span class="token operator">/</span>NavigationBar<span class="token operator">></span> <span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">return</span> <span class="token punctuation">(</span> <span class="token operator"><</span>PageLayout topBar<span class="token operator">=</span><span class="token punctuation">{</span>topBar<span class="token punctuation">}</span> content<span class="token operator">=</span><span class="token punctuation">{</span>content<span class="token punctuation">}</span> <span class="token operator">/</span><span class="token operator">></span> <span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> |
Bạn có thể thấy chúng ta không thể giới hạn một component chỉ có 1 component con, mà nó có thể truyền nhiều Component con một lúc hoặc là nhiều cách xem tại đây. Đây là một ví dụ cho việc chia nhỏ Component thành nhiều thành phần ngay ở Component cha, nếu như muốn giao tiếp với Component cha (sử dụng các method, state, props của component cha) thì các bạn có thể sử dụng Render Props.
Tuy nhiên trong thực tế thì nó không đơn giản như những ví dụ chúng ta vừa xem, mà sẽ phức tạp hơn nhiều, ví dụ như việc chia sẽ data không hẳn chỉ trong Component Cha và Con mà phải chia sẻ ở nhiều cấp độ khác nhiều, giữa nhiều component ngang hàng với nhau. Và Context có thể giúp các component có thể sử dụng chung data, nếu data trong Context thay đổi thì ở các Component sử dụng sẽ thay đổi theo.
Hôm nay chúng ta dừng ở việc Context là gì và khi nào thì sử dụng nó, bài sau sẽ nói về cách sử dụng nha mn.