من المترجم:
أقدم ترجمة مجانية لمقال حول كيفية تنفيذ حل فعال لاستبدال Redux بسياق React والسنانير. إشارة إلى الأخطاء في الترجمة أو النص هو موضع ترحيب. استمتع بمشاهدتك
منذ إصدار API Context الجديد في React 16.3.0 ، سأل الكثير من الناس أنفسهم عما إذا كانت API الجديدة جيدة بما يكفي لاعتبارها بديلاً لـ Redux؟ اعتقدت نفس الشيء ، لكنني لم أفهم تمامًا حتى بعد إصدار الإصدار 16.8.0 مع السنانير. أحاول استخدام التقنيات الشائعة ، فالمسار لا يفهم دائمًا مجموعة كاملة من المشكلات التي يحلونها ، لذلك أنا معتاد على Redux.
وهكذا حدث أنني اشتركت في نشرة
Kent C. Dodds ، واكتشفت بعض رسائل البريد الإلكتروني حول موضوع السياق وإدارة الدولة. بدأت في قراءة .... وقراءة ... وبعد 5 وظائف بلوق ، شيء النقر.
لفهم جميع المفاهيم الأساسية وراء ذلك ، سنقوم بإنشاء زر ، من خلال النقر على الذي سوف نتلقى النكات مع
icanhazdadjoke وعرضها. هذا مثال صغير ولكنه كافٍ.
للتحضير ، لنبدأ بنصائح عشوائية على ما يبدو.
أولاً ، اسمحوا لي أن أقدم صديقي
console.count
:
console.count('Button')
سنقوم بإضافة مكالمة
console.count
إلى كل مكون لمعرفة عدد المرات التي يتم تقديمها. رائع جدا ، هاه؟
ثانياً ، عند إعادة تقديم مكون React ،
لا يعيد تقديم المحتوى الذي تم تمريره
children
.
function Parent({ children }) { const [count, setCount] = React.useState(0) console.count('Parent') return ( <div> <button type="button" onClick={() => { setCount(count => count + 1) }}> Force re-render </button> {children} </div> ) } function Child() { console.count('Child') return <div /> } function App() { return ( <Parent> <Child /> </Parent> ) }
بعد بضع نقرات على الزر ، سترى المحتويات التالية في وحدة التحكم:
Parent: 1 Child: 1 Parent: 2 Parent: 3 Parent: 4
ضع في اعتبارك أن هذه طريقة يتم تجاهلها غالبًا لتحسين أداء التطبيق الخاص بك.
الآن وقد أصبحنا جاهزين ، فلنقم بإنشاء الهيكل العظمي لتطبيقنا:
import React from 'react' function Button() { console.count('Button') return ( <button type="button"> Fetch dad joke </button> ) } function DadJoke() { console.count('DadJoke') return ( <p>Fetched dad joke</p> ) } function App() { console.count('App') return ( <div> <Button /> <DadJoke /> </div> ) } export default App
يجب أن يتلقى
Button
منشأ إجراء (تقريبًا. Creator. الترجمة مأخوذة من
وثائق Redux باللغة الروسية ) والتي ستتلقى مزحة. يجب أن يحصل
DadJoke
على الحالة ،
App
يعرض
App
كلا المكونين باستخدام سياق الموفر.
أنشئ الآن مكونًا مخصصًا
DadJokeProvider
، والذي
DadJokeProvider
داخله الحالة ويلتف المكونات الفرعية في موفر السياق. تذكر أن تحديث حالته لن يعيد تقديم التطبيق بالكامل بسبب تحسين الأطفال أعلاه في React.
لذلك ، قم بإنشاء ملف
contexts/dad-joke.js
:
import React from 'react' const DadJokeContext = React.createContext() export function DadJokeContextProvider({ children }) { const state = { dadJoke: null } const actions = { fetchDadJoke: () => {}, } return ( <DadJokeContext.Provider value={{ state, actions }}> {children} </DadJokeContext.Provider> ) }
نحن أيضا تصدير 2 السنانير للحصول على القيمة من السياق.
export function useDadJokeState() { return React.useContext(DadJokeContext).state } export function useDadJokeActions() { return React.useContext(DadJokeContext).actions }
الآن يمكننا تنفيذ هذا:
import React from 'react' import { DadJokeProvider, useDadJokeState, useDadJokeActions, } from './contexts/dad-joke' function Button() { const { fetchDadJoke } = useDadJokeActions() console.count('Button') return ( <button type="button" onClick={fetchDadJoke}> Fetch dad joke </button> ) } function DadJoke() { const { dadJoke } = useDadJokeState() console.count('DadJoke') return ( <p>{dadJoke}</p> ) } function App() { console.count('App') return ( <DadJokeProvider> <Button /> <DadJoke /> </DadJokeProvider> ) } export default App
هنا! بفضل API التي قطعناها على أنفسنا باستخدام السنانير. لن نقوم بعد الآن بإجراء أي تغييرات على هذا الملف طوال فترة النشر.
لنبدأ بإضافة وظيفة إلى ملف السياق الخاص بنا ، بدءًا من حالة
DadJokeProvider
. نعم ، يمكننا فقط استخدام خطاف
useState
، لكن بدلاً من ذلك ندير حالتنا من خلال
reducer
ببساطة عن طريق إضافة وظائف
Redux
المعروفة والمحبوبة لنا.
function reducer(state, action) { switch (action.type) { case 'SET_DAD_JOKE': return { ...state, dadJoke: action.payload, } default: return new Error(); } }
الآن يمكننا تمرير هذا المخفض إلى ربط
useReducer
والحصول على النكات مع API:
export function DadJokeProvider({ children }) { const [state, dispatch] = React.useReducer(reducer, { dadJoke: null }) async function fetchDadJoke() { const response = await fetch('https://icanhazdadjoke.com', { headers: { accept: 'application/json', }, }) const data = await response.json() dispatch({ type: 'SET_DAD_JOKE', payload: data.joke, }) } const actions = { fetchDadJoke, } return ( <DadJokeContext.Provider value={{ state, actions }}> {children} </DadJokeContext.Provider> ) }
يجب أن تعمل! انقر على زر يجب أن تتلقى وعرض النكات!
دعنا نتحقق من وحدة التحكم:
App: 1 Button: 1 DadJoke: 1 Button: 2 DadJoke: 2 Button: 3 DadJoke: 3
يتم إعادة تقديم كلا المكونين في كل مرة يتم فيها تحديث الحالة ، ولكن أحدها فقط يستخدمها فعليًا. تخيل تطبيقًا حقيقيًا يستخدم فيه المئات من المكونات الإجراءات فقط. هل سيكون من الرائع لو استطعنا تقديم كل هذه العروض الاختيارية؟
وهنا ندخل في إقليم المساواة النسبية ، لذلك تذكير صغير:
const obj = {}
سيتم إعادة تقديم مكون يستخدم السياق في كل مرة تتغير فيها قيمة هذا السياق. دعنا ننظر إلى معنى مزود السياق الخاص بنا:
<DadJokeContext.Provider value={{ state, actions }}>
هنا نقوم بإنشاء كائن جديد أثناء كل عارض ، ولكن هذا أمر لا مفر منه ، لأنه سيتم إنشاء كائن جديد في كل مرة نقوم فيها بتنفيذ (
dispatch
) ، لذلك من المستحيل ببساطة تخزين (القيمة) هذه القيمة.
كل هذا يبدو وكأنه نهاية القصة ، أليس كذلك؟
إذا نظرنا إلى وظيفة
fetchDadJoke
، فإن الشيء الوحيد الذي تستخدمه من النطاق الخارجي هو
dispatch
، أليس كذلك؟ بشكل عام ، سأخبرك بسر بسيط عن الوظائف التي تم إنشاؤها في
useReducer
و
useState
. للإيجاز ،
useState
كمثال:
let prevSetCount function Counter() { const [count, setCount] = React.useState() if (typeof prevSetCount !== 'undefined') { console.log(setCount === prevSetCount) } prevSetCount = setCount return ( <button type="button" onClick={() => { setCount(count => count + 1) }}> Increment </button> ) }
انقر فوق الزر عدة مرات وانظر إلى وحدة التحكم:
true true true
ستلاحظ أن
setCount
نفس الوظيفة لكل تقديم. وهذا ينطبق أيضا على وظيفة
dispatch
لدينا.
هذا يعني أن وظيفة
fetchDadJoke
بنا
fetchDadJoke
تعتمد على أي شيء يتغير مع مرور الوقت ، ولا تعتمد على أي
fetchDadJoke
الإجراءات الآخرين ، لذلك يجب إنشاء كائن الإجراء مرة واحدة فقط ، في أول عرض:
const actions = React.useMemo(() => ({ fetchDadJoke, }), [])
الآن بعد أن أصبح لدينا كائن تم تخزينه مؤقتًا مع إجراءات ، هل يمكننا تحسين قيمة السياق؟ في الواقع ، لا ، لأنه بغض النظر عن مدى جودة كائن القيمة ، ما زلنا بحاجة إلى إنشاء كائن جديد في كل مرة بسبب تغييرات الحالة. ومع ذلك ، ماذا لو نقلنا كائن إجراء من سياق موجود إلى سياق جديد؟ من قال أنه يمكن أن يكون لدينا سياق واحد فقط؟
const DadJokeStateContext = React.createContext() const DadJokeActionsContext = React.createContext()
يمكننا الجمع بين كلا السياقين في
DadJokeProvider
لدينا:
return ( <DadJokeStateContext.Provider value={state}> <DadJokeActionsContext.Provider value={actions}> {children} </DadJokeActionsContext.Provider> </DadJokeStateContext.Provider> )
وقرص السنانير لدينا:
export function useDadJokeState() { return React.useContext(DadJokeStateContext) } export function useDadJokeActions() { return React.useContext(DadJokeActionsContext) }
ونحن انتهينا! على محمل الجد ، قم بتنزيل العديد من النكات التي تريدها وشاهدها بنفسك.
App: 1 Button: 1 DadJoke: 1 DadJoke: 2 DadJoke: 3 DadJoke: 4 DadJoke: 5
لذلك قمت بتنفيذ حل إدارة الدولة الأمثل الخاص بك! يمكنك إنشاء موفرين مختلفين باستخدام هذا القالب ثنائي السياق لإنشاء تطبيقك ، ولكن هذا ليس كل شيء ، يمكنك أيضًا تقديم مكون الموفر نفسه عدة مرات! ماذا؟ نعم ، جرب تقديم
DadJokeProvider
في العديد من الأماكن
DadJokeProvider
كيف يتم تطبيق إدارة الولاية بسهولة.
أطلق العنان لخيالك وراجع سبب احتياجك إلى
Redux
.
بفضل Kent C. Dodds لمقالات في قالب السياقين. لم أره في أي مكان آخر ويبدو لي أن هذا يغير قواعد اللعبة.
اقرأ منشورات مدونة Kent التالية للحصول على مزيد من المعلومات حول المفاهيم التي تحدثت عنها:
متى استخدام useMemo و useCallbackكيفية تحسين قيمة السياقكيفية استخدام سياق التفاعل بشكل فعالإدارة حالة التطبيق في React.خدعة واحدة بسيطة لتحسين إعادة تقديم العروض في React