غير معلن التالي: إدارة الحالة الحد الأدنى لتطبيق React

200 بايت لإدارة حالة مكونات React

  • رد فعل السنانير : هذا كل ما يتطلبه الأمر لإدارة الحالة.
  • ~ 200 بايت ، دقيقة + gz.
  • واجهة برمجة تطبيقات مألوفة : استخدم React كالمعتاد.
  • الحد الأدنى لواجهة برمجة التطبيقات : خمس دقائق تكفي لمعرفة ذلك.
  • مكتوب في TypeScript لتوفير الاستدلال التلقائي للنوع.

السؤال الرئيسي هو: هل هذه الحزمة أفضل من Redux؟ حسنا ...


  • هو أقل. إنه أصغر 40 مرة.
  • إنه أسرع. عزل مشاكل الأداء على مستوى المكون.
  • إنه أسهل في التعلم. في أي حال ، تحتاج إلى أن تكون قادرًا على استخدام خطافات React والسياق ، فهي رائعة.
  • انه من الاسهل للاندماج. قم بتوصيل مكون واحد في وقت واحد دون انقطاع التوافق مع مكتبات React الأخرى.
  • إنه أسهل للاختبار. اختبار مخفضات بشكل منفصل هو مضيعة للوقت ؛ تبسيط اختبار مكونات React أنفسهم.
  • إنه أبسط من حيث الكتابة. هو مكتوب وذلك لتعظيم استخدام نوع الاستدلال.
  • هو أضيق الحدود. هذا مجرد رد فعل.

مثال رمز


import React, { useState } from "react" import { createContainer } from "unstated-next" import { render } from "react-dom" function useCounter() { let [count, setCount] = useState(0) let decrement = () => setCount(count - 1) let increment = () => setCount(count + 1) return { count, decrement, increment } } let Counter = createContainer(useCounter) function CounterDisplay() { let counter = Counter.useContainer() return ( <div> <button onClick={counter.decrement}>-</button> <span>{counter.count}</span> <button onClick={counter.increment}>+</button> </div> ) } function App() { return ( <Counter.Provider> <CounterDisplay /> <CounterDisplay /> </Counter.Provider> ) } render(<App />, document.getElementById("root")) 

الموقف تجاه غير مذكور


أنا (جيمي كايل - تقريبًا.) اعتبر هذه المكتبة خليفة لـ Unstated . فعلت Unstated لأنني كنت مقتنعا بأن React نفسها قامت بعمل رائع لإدارة الدولة ، وأنها تفتقر إلى آلية بسيطة فقط لفصل الحالة العامة والمنطق. لذلك ، قمت بإنشاء Unstated كحل "الحد الأدنى" لهذه المشكلة.


مع ظهور السنانير ، أصبح React أفضل بكثير من حيث تسليط الضوء على الحالة العامة والمنطق. أفضل بكثير ، من وجهة نظري ، أصبحت Unstated عبارة عن تجريد غير ضروري.


ليس أقل ، أعتقد أن العديد من المطورين لديهم فكرة قليلة عن كيفية فصل المنطق والحالة العامة للتطبيق باستخدام علاقات React. قد يكون هذا ببساطة بسبب عدم كفاية جودة الوثائق والقصور الذاتي في المجتمع ، لكنني أعتقد أن واجهة برمجة تطبيقات واضحة قادرة على تصحيح هذا القصور.


غير مذكور التالي هو هذا API جدا. بدلاً من أن يكون "الحد الأدنى لواجهة برمجة التطبيقات لمشاركة الحالة والمنطق في React" ، أصبح لديه الآن "الحد الأدنى لواجهة برمجة التطبيقات لفهم كيفية مشاركة الحالة والمنطق في React."


أنا حقا أحب React ، أريد React أن تزدهر. أفضل أن يتخلى المجتمع عن استخدام المكتبات الخارجية لإدارة الحالة مثل Redux ، وأن يبدأ أخيرًا في استخدام الأدوات المدمجة في React بالكامل.


إذا كنت تستخدم React - بدلاً من استخدام Unstated - سأرحب بهذا. اكتب عنها في مدونتك! تحدث عن ذلك في المؤتمرات! مشاركة علمك مع المجتمع.


دليل غير معلن


إذا لم تكن معتادًا على React hooks حتى الآن ، فإنني أوصي بأن تتوقف عن القراءة والقراءة
وثائق ممتازة على موقع React .


لذلك ، بمساعدة الخطافات ، يمكنك كتابة شيء مثل هذا المكون:


 function CounterDisplay() { let [count, setCount] = useState(0) let decrement = () => setCount(count - 1) let increment = () => setCount(count + 1) return ( <div> <button onClick={decrement}>-</button> <p>You clicked {count} times</p> <button onClick={increment}>+</button> </div> ) } 

إذا احتاج منطق المكون إلى استخدامه في عدة أماكن ، فيمكن إزالته
في ربط مخصص منفصل:


 function useCounter() { let [count, setCount] = useState(0) let decrement = () => setCount(count - 1) let increment = () => setCount(count + 1) return { count, decrement, increment } } function CounterDisplay() { let counter = useCounter() return ( <div> <button onClick={counter.decrement}>-</button> <p>You clicked {counter.count} times</p> <button onClick={counter.increment}>+</button> </div> ) } 

ولكن ماذا تفعل عندما تحتاج إلى حالة عامة ، وليس فقط المنطق؟
السياق مفيد هنا:


 function useCounter() { let [count, setCount] = useState(0) let decrement = () => setCount(count - 1) let increment = () => setCount(count + 1) return { count, decrement, increment } } let Counter = createContext(null) function CounterDisplay() { let counter = useContext(Counter) return ( <div> <button onClick={counter.decrement}>-</button> <p>You clicked {counter.count} times</p> <button onClick={counter.increment}>+</button> </div> ) } function App() { let counter = useCounter() return ( <Counter.Provider value={counter}> <CounterDisplay /> <CounterDisplay /> </Counter.Provider> ) } 

إنه رائع ورائع. كلما زاد عدد الناس الذين يكتبون بهذا الأسلوب ، كان ذلك أفضل.


ومع ذلك ، يجدر إضافة بنية ووضوح أكثر قليلاً حتى يعبر API عن نواياك بوضوح تام.


للقيام بذلك ، أضفنا وظيفة createContainer() ، بحيث يمكنك التعامل مع السنانير المخصصة الخاصة بك على أنها "حاويات" ، بحيث من المستحيل ببساطة استخدام واجهة برمجة التطبيقات الواضحة والواضحة.


 import { createContainer } from "unstated-next" function useCounter() { let [count, setCount] = useState(0) let decrement = () => setCount(count - 1) let increment = () => setCount(count + 1) return { count, decrement, increment } } let Counter = createContainer(useCounter) function CounterDisplay() { let counter = Counter.useContainer() return ( <div> <button onClick={counter.decrement}>-</button> <p>You clicked {counter.count} times</p> <button onClick={counter.increment}>+</button> </div> ) } function App() { return ( <Counter.Provider> <CounterDisplay /> <CounterDisplay /> </Counter.Provider> ) } 

قارن نص المكون قبل وبعد التغييرات التي أجريناها:


 - import { createContext, useContext } from "react" + import { createContainer } from "unstated-next" function useCounter() { ... } - let Counter = createContext(null) + let Counter = createContainer(useCounter) function CounterDisplay() { - let counter = useContext(Counter) + let counter = Counter.useContainer() return ( <div> ... </div> ) } function App() { - let counter = useCounter() return ( - <Counter.Provider value={counter}> + <Counter.Provider> <CounterDisplay /> <CounterDisplay /> </Counter.Provider> ) } 

إذا كتبت في TypeScript (وإذا لم يكن كذلك ، فإنني أوصي بشدة أن تتعرف عليه) ، ستحصل أيضًا على استنتاج أفضل للكتابة. إذا تمت كتابة الخطاف المخصص الخاص بك بقوة ، فإن إخراج جميع الأنواع الأخرى سيعمل تلقائيًا.


API


createContainer(useHook)


 import { createContainer } from "unstated-next" function useCustomHook() { let [value, setValue] = useState() let onChange = e => setValue(e.currentTarget.value) return { value, onChange } } let Container = createContainer(useCustomHook) // Container === { Provider, useContainer } 

<Container.Provider>


 function ParentComponent() { return ( <Container.Provider> <ChildComponent /> </Container.Provider> ) } 

Container.useContainer()


 function ChildComponent() { let input = Container.useContainer() return <input value={input.value} onChange={input.onChange} /> } 

useContainer(Container)


 import { useContainer } from "unstated-next" function ChildComponent() { let input = useContainer(Container) return <input value={input.value} onChange={input.onChange} /> } 

نصائح


نصيحة رقم 1: دمج الحاويات


نظرًا لأننا نتعامل مع السنانير المخصصة ، يمكننا الجمع بين الحاويات داخل السنانير الأخرى.


 function useCounter() { let [count, setCount] = useState(0) let decrement = () => setCount(count - 1) let increment = () => setCount(count + 1) return { count, decrement, increment, setCount } } let Counter = createContainer(useCounter) function useResettableCounter() { let counter = Counter.useContainer() let reset = () => counter.setCount(0) return { ...counter, reset } } 

نصيحة رقم 2: استخدام الحاويات الصغيرة


من الأفضل جعل الحاويات صغيرة ومركزة بوضوح على مهمة محددة. إذا كنت بحاجة إلى منطق عمل إضافي في حاويات - فقم بإجراء عمليات جديدة في خطافات منفصلة ، ودع الحالة يتم تخزينها في حاويات.


 function useCount() { return useState(0) } let Count = createContainer(useCount) function useCounter() { let [count, setCount] = Count.useContainer() let decrement = () => setCount(count - 1) let increment = () => setCount(count + 1) let reset = () => setCount(0) return { count, decrement, increment, reset } } 

نصيحة رقم 3: تحسين المكونات


لا يوجد "تحسين" منفصل unstated-next ؛ الأساليب المعتادة لتحسين مكونات React كافية.


1) الأمثل من الأشجار الفرعية الثقيلة عن طريق تقسيم المكونات.


إلى:


 function CounterDisplay() { let counter = Counter.useContainer() return ( <div> <button onClick={counter.decrement}>-</button> <p>You clicked {counter.count} times</p> <button onClick={counter.increment}>+</button> <div> <div> <div> <div>   </div> </div> </div> </div> </div> ) } 

بعد:


 function ExpensiveComponent() { return ( <div> <div> <div> <div>   </div> </div> </div> </div> ) } function CounterDisplay() { let counter = Counter.useContainer() return ( <div> <button onClick={counter.decrement}>-</button> <p>You clicked {counter.count} times</p> <button onClick={counter.increment}>+</button> <ExpensiveComponent /> </div> ) } 

2) تحسين العمليات الثقيلة باستخدام خطاف useMemo ()


إلى:


 function CounterDisplay(props) { let counter = Counter.useContainer() //    ,   `counter` —   let expensiveValue = expensiveComputation(props.input) return ( <div> <button onClick={counter.decrement}>-</button> <p>You clicked {counter.count} times</p> <button onClick={counter.increment}>+</button> </div> ) } 

بعد:


 function CounterDisplay(props) { let counter = Counter.useContainer() //    ,     let expensiveValue = useMemo(() => { return expensiveComputation(props.input) }, [props.input]) return ( <div> <button onClick={counter.decrement}>-</button> <p>You clicked {counter.count} times</p> <button onClick={counter.increment}>+</button> </div> ) } 

3) تقليل عدد re-reenders باستخدام React.memo () و useCallback ()


إلى:


 function useCounter() { let [count, setCount] = useState(0) let decrement = () => setCount(count - 1) let increment = () => setCount(count + 1) return { count, decrement, increment } } let Counter = createContainer(useCounter) function CounterDisplay(props) { let counter = Counter.useContainer() return ( <div> <button onClick={counter.decrement}>-</button> <p>You clicked {counter.count} times</p> <button onClick={counter.increment}>+</button> </div> ) } 

بعد:


 function useCounter() { let [count, setCount] = useState(0) let decrement = useCallback(() => setCount(count - 1), [count]) let increment = useCallback(() => setCount(count + 1), [count]) return { count, decrement, increment } } let Counter = createContainer(useCounter) let CounterDisplayInner = React.memo(props => { return ( <div> <button onClick={props.decrement}>-</button> <p>You clicked {props.count} times</p> <button onClick={props.increment}>+</button> </div> ) }) function CounterDisplay(props) { let counter = Counter.useContainer() return <CounterDisplayInner {...counter} /> } 

الهجرة مع unstated


لقد قمت عن قصد بنشر هذه المكتبة كحزمة منفصلة لأن API بالكامل جديدة تمامًا. لذلك ، يمكنك تثبيت كلتا الحزمتين بالتوازي والترحيل تدريجيًا.


شارك انطباعاتك عن الانتقال إلى unstated-next ، لأنه خلال الأشهر القليلة المقبلة أخطط للقيام بأمرين بناءً على هذه المعلومات:


  • تأكد من أن unstated-next يلبي جميع احتياجات المستخدمين unstated المحددين.
  • تأكد من عدم وجود عملية واضحة وموجزة unstated-next .

ربما سأضيف بعض واجهات برمجة التطبيقات إلى المكتبة القديمة أو الجديدة لتسهيل حياة المطورين. أما بالنسبة إلى unstated-next ، فأعدك بأن واجهات برمجة التطبيقات المضافة ستكون الحد الأدنى قدر الإمكان ، وسأبذل قصارى جهدي للحفاظ على المكتبة صغيرة.


في المستقبل ، سأقوم على الأرجح unstated-next الكود unstated-next unstated مرة أخرى إلى الإصدار الرئيسي الجديد. سيظل unstated-next متاحًا بحيث يمكنك استخدام unstated@2 و unstated-next في نفس المشروع بالتوازي. بعد ذلك ، عند الانتهاء من الترحيل ، يمكنك الترقية إلى unstated@3 وحذفه unstated-next (بالطبع ، يجب تحديث جميع الواردات ... يجب أن يكون هناك ما يكفي من البحث والاستبدال).


على الرغم من التغيير الهائل في واجهة برمجة التطبيقات ، آمل أن أتمكن من تزويدك بأسهل عملية ترحيل ممكنة. سأكون سعيدًا بأي تعليقات على ما يمكن القيام به بشكل أفضل.


مراجع


Source: https://habr.com/ru/post/ar451410/


All Articles