Redux-不需要! 在React中替换为useContext和useReducer?

图片


美好的一天,哈布罗夫斯克!


我想谈谈我最近如何了解React中的某些“陷阱”。 它们相对较新,出现在2019年2月6日的版本[16.8.0]中(根据FrontEnd的开发速度,它已经很久了)


阅读文档后,我专注于useReducer挂钩,并立即问自己一个问题: “这东西可以完全取代Redux !?” 我花了几个晚上进行实验,现在我想分享结果和结论。


我需要用useContext + useReducer替换Redux吗?


对于急躁的人-立即得出结论


对于:


  • 您可以在小型应用程序(无需大型组合的Reducers)中使用钩子(useContext + useReducer)代替Redux。 在这种情况下,Redux可能确实是多余的。

反对:


  • 已经在大量的React + Redux上编写了大量代码,并且至少现在不建议将其重写为钩子(useContext + useReducer)。
  • Redux是一个经过验证的库,钩子是一种创新,它们的接口和行为将来可能会改变。
  • 为了使useContext + useReducer真正方便,您将不得不编写一些自行车。

结论是作者的个人观点,并不声称它是无条件的事实-如果您不同意,我会很高兴看到您的建设性评论。


让我们尝试找出答案


让我们从一个简单的例子开始。


(reducer.js)


import React from "react"; export const ContextApp = React.createContext(); export const initialState = { app: { test: 'test_context' } }; export const testReducer = (state, action) => { switch(action.type) { case 'test_update': return { ...state, ...action.payload }; default: return state } }; 

到目前为止,我们的reducer看起来与Redux完全相同


(app.js)


 import React, {useReducer} from 'react' import {ContextApp, initialState, testReducer} from "./reducer.js"; import {IndexComponent} from "./IndexComponent.js" export const App = () => { //  reducer   state + dispatch   const [state, dispatch] = useReducer(testReducer, initialState); return ( //  ,     reducer   //  ContextApp   (dispatch  state) //      <ContextApp.Provider value={{dispatch, state}}> <IndexComponent/> </ContextApp.Provider> ) }; 

(IndexComponent.js)


 import React, {useContext} from "react"; import {ContextApp} from "./reducer.js"; export function IndexComponent() { //   useContext    ContextApp //  IndexComponent      ContextApp.Provider const {state, dispatch} = useContext(ContextApp); return ( //  dispatch    reducer.js   testReducer //    .    Redux <div onClick={() => {dispatch({ type: 'test_update', payload: { newVar: 123 } })}}> {JSON.stringify(state)} </div> ) } 

这是最简单的例子 更新 将新数据写入平面(无嵌套)Reducer
从理论上讲,您甚至可以尝试这样写:


(reducer.js)


 ... export const testReducer = (state, data) => { return { ...state, ...data } ... 

(IndexComponent.js)


 ... return ( //      ,   type <div onClick={() => {dispatch({ newVar: 123 }> {JSON.stringify(state)} </div> ) ... 

如果我们没有大型而简单的应用程序(实际上是很少见的),那么您将无法使用类型,而总是直接通过操作来管理reducer的归约。 顺便说一句,在这种情况下,我们只在reducer中写入了新数据,而以更新为代价,但是如果我们必须在具有多个嵌套级别的树中更改一个值,该怎么办?


现在比较复杂


让我们看下面的例子:


(IndexComponent.js)


 ... return ( //        //     -     //      ,     callback: <div onClick={() => { //  ,    callback, //   testReducer     state (state) => { const {tree_1} = state; return { tree_1: { ...tree_1, tree_2_1: { ...tree_1.tree_2_1, tree_3_1: 'tree_3_1 UPDATE' }, }, }; }> {JSON.stringify(state)} </div> ) ... 

(reducer.js)


 ... export const initialState = { tree_1: { tree_2_1: { tree_3_1: 'tree_3_1', tree_3_2: 'tree_3_2' }, tree_2_2: { tree_3_3: 'tree_3_3', tree_3_4: 'tree_3_4' } } }; export const testReducer = (state, callback) => { //      state      //      callback const action = callback(state); return { ...state, ...action } ... 

好的,我们也知道了树更新。 尽管在这种情况下, 最好返回使用 testReducer内部的类型并根据某种操作类型更新树。 一切都像在Redux中一样,只是生成的包稍微小一些[8]。


异步操作和调度


但是,一切都还好吗? 如果我们去使用异步操作会怎样?
为此,我们必须定义自己的调度。 试试吧!


(action.js)


 export const actions = { sendToServer: function ({dataForServer}) { //      ,   dispatch return function (dispatch) { //   dispatch    , //   state      dispatch(state => { return { pending: true } }); } } 

(IndexComponent.js)


 const [state, _dispatch] = useReducer(AppReducer, AppInitialState); //     dispatch   -> //    ,  Proxy const dispatch = (action) => action(_dispatch); ... dispatch(actions.sendToServer({dataForServer: 'data'})) ... 

一切似乎都还不错, 但是现在我们有很多回调嵌套 ,这不是很酷,如果我们只想更改状态而不创建动作函数,我们将不得不编写这种构造:


(IndexComponent.js)


 ... dispatch( (dispatch) => dispatch(state => { return { {dataForServer: 'data'} } }) ) ... 

原来有些可怕,对吧? 为了简单地更新数据,我非常想写这样的东西:


(IndexComponent.js)


 ... dispatch({dataForServer: 'data'}) ... 

为此,您将不得不更改我们之前创建的调度功能的代理
(IndexComponent.js)


 const [state, _dispatch] = useReducer(AppReducer, AppInitialState); //  // const dispatch = (action) => action(_dispatch); //  const dispatch = (action) => { if (typeof action === "function") { action(_dispatch); } else { _dispatch(() => action) } }; ... 

现在,我们既可以传递动作函数,也可以传递简单的对象来进行分派。
但是! 通过简单的对象传输,您必须小心,您可能会这样做:


(IndexComponent.js)


 ... dispatch({ tree: { //  state         AppContext ...state.tree, data: 'newData' } }) ... 

为什么这个例子不好? 通过这样的事实,即在处理此分派时,状态可能已经通过另一个分派进行了更新,但是这些更改尚未到达我们的组件,实际上,我们正在使用旧的状态实例,该实例将用旧数据覆盖所有内容。


因此,这种方法几乎在任何地方都几乎不适用,仅适用于更新没有嵌套且您不需要使用状态来更新嵌套对象的Flat Reducer。 实际上,reducer很少会完美地平坦,因此我建议您不要使用此方法,而只能通过操作来更新数据。


(action.js)


 ... // ..  dispatch   callback,    //       (. reducer.js) dispatch(state => { return { dataFromServer: { ...state.dataFromServer, form_isPending: true } } }); axios({ method: 'post', url: `...`, data: {...} }).then(response => { dispatch(state => { //   axios     //         dispatch //     ,  state -    , // ..       testReducer (reducer.js) return { dataFromServer: { ...state.dataFromServer, form_isPending: false, form_request: response.data }, user: {} } }); }).catch(error => { dispatch(state => { // , state -    ) return { dataFromServer: { ...state.dataFromServer, form_isPending: false, form_request: { error: error.response.data } }, } }); ... 

结论:


  • 这是一次有趣的经历,我加强了我的学术知识并了解了反应的新特征
  • 我不会在生产中使用这种方法(至少在接下来的六个月中)。 由于上述原因(这是一项新功能,并且Redux是一种经过验证的可靠工具)+我没有性能问题,可以追寻放弃编辑器可以赢得的毫秒数[8]

在评论中,我将很高兴知道我们Habrosobschestva前端部分的同事的意见!


参考文献:


Source: https://habr.com/ru/post/zh-CN473070/


All Articles