Published on

这样的react代码不好闻

Authors
  • avatar
    Name
    noodles
    每个人的花期不同,不必在乎别人比你提前拥有

本文是react思考系列的第二篇,在之前的对React的一点思考 中梳理了react的哲学:

  • 隔离DOM
  • 对功能复用的思考
  • 渲染模式的探索
  • 超越UI框架的一些探索
    在文章的结尾梳理了在react使用过程的一些思考,关于如何在团队中维护相对统一的react代码风格.本篇基于之前的内容展开,关于 '好'的代码风格也许每个人的看法是不尽相同的,那么关于'坏'味道的代码风格也许在一定程度能达到共识. 本文梳理在日常开发中遇到的一些'坏'味道的代码,会从它为什么是'坏'味道的代码角度来分析原因并且尝试提供一些解决的思路

hooks地狱

    export default function Home() {
        const [loading, setLoading] = useState(false)
        const [name, setName] = useState('')
        const [userInfo, setUserInfo] = useState({})
        const [effectData, setEffectData] = useState<number[]>([])
        const [overviewData, setOverViewData] = useState<number[]>([])

        useEffect(() => {
            // api获取逻辑
            setEffectData([1,2])
            setOverViewData([4,5])
        }, [userInfo])

        // 也许还有其他的useCallback/useMemo等
        return (
            <div>ss</div>
        );
    }

react的hooks在代码复用和赋予组件数据能力上都相对好用且轻量,但是hooks的'滥'用问题在代码维护上和组件性能上都有一定影响. 上面的代码是一段示例代码,这里定义了多个state去维护页面状态,还可能有useEffect/useCallback等其他hooks逻辑.这样的代码在维护上会有一些问题:

  • 可读性差
    • useEffect/useCallback/useMemo等hooks取决于依赖的更新触发.随着依赖的增加代码可维护性降低
    • 声明式的写法相对于命令式的写法并不符合思维习惯,在阅读和表达hooks的时候,在对原有的逻辑理解不够充分容易造成代码逻辑混乱.
    • 组件承接了较多的数据逻辑,这部分逻辑在组件维度上看很难集中管理,在涉及到数据需要共享等场景就需要对代码进行改造
  • 性能问题 过多的hooks意味着页面需要承载更多的计算执行逻辑,也许有时候你不需要hook

笔者在实际的项目中遇到过一个页面有10多个useState的页面,看起来真的很头大.通过将组件拆分/相关state合并手段等,页面的重复渲染就下降了. 在hooks地狱这个问题的解法上:

context/useReducer

    // 多层级的context嵌套
    <AContext>
        <BContext>
            <CContext>
                <DContext>
                    <SubChild />
                </DContext>
            </Context>
        </BContext>           
    </AContext>

react提供的context是一种轻量级的状态管理方案,一般在使用方法通常是一个顶层的Context容器或者多层包裹的Context容器.Context 在相应Context更新时,订阅该Context的组件都会进行渲染.Context在重复渲染/变更逻辑回溯上有存在一定问题,不大推荐使用Context方案做数据管理.

    // useReducer例子
    const initialState = {
        username: '',
        email: '',
        password: ''
    };
    function reducer(state, action) {
        switch (action.type) {
            case 'SET_FIELD':
            return {
                ...state,
                [action.field]: action.value
            };
            case 'RESET':
            return initialState;
            default:
            throw new Error();
        }
    }
    const [state, dispatch] = useReducer(reducer, initialState);
    //在页面中通过dispatch触发更新
    // dispatch({ type: 'SET_FIELD', value: 111  })

useReducer也是一种轻量化的状态管理方案,它是一种'类'Redux方案.但是它缺少Redux的CombineReducer等能力,在状态管理的组织上存在一定缺陷.在中大型系统上不推荐使用useReducer方案.

context + form

context和Form
笔者在最近的项目中就修改了这样的一个react context结合form的渲染场景.在前端的Form表单组件内通常有两部分内容:
  • 表单组件的渲染逻辑
  • 表单联动逻辑 渲染逻辑和联动逻辑如果没有好好组织就已经容易产生渲染问题,结合上Context出现页面卡顿是可以预期的.不推荐使用Context + form的方案,推荐可以细粒度控制更新的状态管理库例如mobx/zustand

巨型组件

当一个组件承载过多的功能的时候,它已经变成巨型组件.巨型组件由于内部包裹较多组件或者动态生成逻辑等存在一定性能问题,推荐按功能去拆分组件,一些辅助的解法比如限制文件的长度/代码片段等.
Container
在上面的例子中通过对组件功能的拆分,就能比较细粒度的控制组件的渲染,在代码可读性上也有一定的提升.