
美好的一天,哈布罗夫斯克!
我想谈谈我最近如何了解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 = () => {
(IndexComponent.js)
import React, {useContext} from "react"; import {ContextApp} from "./reducer.js"; export function IndexComponent() {
这是最简单的例子 更新 将新数据写入平面(无嵌套)Reducer
从理论上讲,您甚至可以尝试这样写:
(reducer.js)
... export const testReducer = (state, data) => { return { ...state, ...data } ...
(IndexComponent.js)
... return (
如果我们没有大型而简单的应用程序(实际上是很少见的),那么您将无法使用类型,而总是直接通过操作来管理reducer的归约。 顺便说一句,在这种情况下,我们只在reducer中写入了新数据,而以更新为代价,但是如果我们必须在具有多个嵌套级别的树中更改一个值,该怎么办?
现在比较复杂
让我们看下面的例子:
(IndexComponent.js)
... return (
(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) => {
好的,我们也知道了树更新。 尽管在这种情况下, 最好返回使用 testReducer内部的类型并根据某种操作类型更新树。 一切都像在Redux中一样,只是生成的包稍微小一些[8]。
异步操作和调度
但是,一切都还好吗? 如果我们去使用异步操作会怎样?
为此,我们必须定义自己的调度。 试试吧!
(action.js)
export const actions = { sendToServer: function ({dataForServer}) {
(IndexComponent.js)
const [state, _dispatch] = useReducer(AppReducer, AppInitialState);
一切似乎都还不错, 但是现在我们有很多回调嵌套 ,这不是很酷,如果我们只想更改状态而不创建动作函数,我们将不得不编写这种构造:
(IndexComponent.js)
... dispatch( (dispatch) => dispatch(state => { return { {dataForServer: 'data'} } }) ) ...
原来有些可怕,对吧? 为了简单地更新数据,我非常想写这样的东西:
(IndexComponent.js)
... dispatch({dataForServer: 'data'}) ...
为此,您将不得不更改我们之前创建的调度功能的代理
(IndexComponent.js)
const [state, _dispatch] = useReducer(AppReducer, AppInitialState);
现在,我们既可以传递动作函数,也可以传递简单的对象来进行分派。
但是! 通过简单的对象传输,您必须小心,您可能会这样做:
(IndexComponent.js)
... dispatch({ tree: {
为什么这个例子不好? 通过这样的事实,即在处理此分派时,状态可能已经通过另一个分派进行了更新,但是这些更改尚未到达我们的组件,实际上,我们正在使用旧的状态实例,该实例将用旧数据覆盖所有内容。
因此,这种方法几乎在任何地方都几乎不适用,仅适用于更新没有嵌套且您不需要使用状态来更新嵌套对象的Flat Reducer。 实际上,reducer很少会完美地平坦,因此我建议您不要使用此方法,而只能通过操作来更新数据。
(action.js)
...
结论:
- 这是一次有趣的经历,我加强了我的学术知识并了解了反应的新特征
- 我不会在生产中使用这种方法(至少在接下来的六个月中)。 由于上述原因(这是一项新功能,并且Redux是一种经过验证的可靠工具)+我没有性能问题,可以追寻放弃编辑器可以赢得的毫秒数[8]
在评论中,我将很高兴知道我们Habrosobschestva前端部分的同事的意见!
参考文献: