React 奇技淫巧——内联hook

本文介绍一种 react 当中在一堆 jsx 中嵌入 hook 的方式 inlineHook

场景 or 为什么

一、列表渲染

列表渲染中,你或许需要为每个元素准备其状态和副作用,这时候你就需要用一个新的组件,如果需要用到父组件的状态,还需要透传一堆 props 给它。也就是说,你为了使用一个组件,需要包一个新的组件出来。

ts 复制代码
function Parent(){
    // states
    // effects 

    return <>
        {list.map(item =>
            <ChildComponentWrap
                key={item.key} 
               // ...state
               // ...callback
               // 这里其实可能很多
            />
        )}
    </>
}

// 一个黑盒组件
function ChildComponent(props){
    return <>...</>
}

// 为了维护它的状态而包装的组件
function ChildComponentWrap(props){
   // states
   // effects
   
   return <ChildComponent
               // ...state
               // ...callback
   />
}

作为一个很擅长堆屎山的人,尝试过下面这样的代码,但是由于 react hook 的规则,元素数量发生变化的时候,必然报错。

tsx 复制代码
function Parent(){
    // states
    // effects 

    return <>
        {list.map(item =>{
              useState()
              useEffect(()=>{}, [xxx]);

              return <ChildComponentWrap
                    key={item.key} 
                   // ...state
                   // ...callback
                   // 这里其实可能很多
                />
        
            }
        )}
    </>
}

所以需要某种方法,让它支持内联 hook,就有了 inlineHook 的这样一个工具,原理很简单,它创造了一个组件,并把你的代码块运行在里面,所以里面可以用 hook,当第一参数为 string 的时候,作为组件的 key。

tsx 复制代码
function Parent(){
    // states
    // effects 

    return <>
        {list.map(item => 
            inlineHook(item.key, () => {
              useState()
              useEffect(()=>{}, [xxx]);

              return <ChildComponent
                   // ...state
                   // ...callback
                />
        
            })
        )}
    </>
}

二、条件副作用

错误代码

tsx 复制代码
function AComponent(){
    
    // 注意这里,condition false true 转换的时候会报错
    if(condition){
        useEffect(()=>{}, [])
    }
    
    return <>...</>
}

使用 inlineHook,基于条件挂载组件的方式就可以实现条件 hook。

tsx 复制代码
function AComponent(){
    
    return <>
        {condition && inlineHook(()=>{
            useEffect(()=>{}, []);
        })}
        ...
    </>
}

三、表单联动

想象这样一个场景,你有一个条件表单。

  1. A 会限制 B 的选项
    • A 选 'a' 时,B 可选 '1'、'2'、'3';
    • A 选 'b' 时,B 可选 '3'、'4'、'5';
    • A 选 'b' 时,B 可选 '5'、'6'、'7';
  2. 当 A 切换的时候,你需要清除已选中的 B选项(注意上面的 B可选项是有重叠的,有些值可以保留)
tsx 复制代码
return <Form>
    <Form.Select
        field='A'
        optionList={['a','b','c']}
    />
    {* 可能离得很远 *}
    <Form.Select
        field='B'
        multiple  // 多选
        optionList={[...]} // 想办法映射一下选项
    />
</Form>

思考一下,怎么弄。


可以在 A 的 onChange 里面处理一下

tsx 复制代码
<Form.Select
    field='A'
    optionList={['a','b','c']}
    onChange={(v) => { // 可以提出去,但是逻辑也很跳跃
        const BOptionList = getBOptionList(v);
        const newValues = ... 
        const values = formApi.getValue('B')
        if(!isEqual(values,newValues)){
           formApi.setValue('B', newBValue)
        }
    }}
/>
{* 可能离得很远,不一定会关注到这两个表单项的关系 *}
 <Form.Select
    field='B'
    multiple  // 多选
    optionList={[...]} // 想办法映射一下选项
/>

也可以拆组件,对吗?如我之前所说,你需要在组件间跳跃,上下文 props 需要传递。

tsx 复制代码
function FieldB(props){
    const AValue = useFieldValue("A");
    const formApi = useFormApi();
    const BOptionList = getBOptionList(AValue);
    
    useEffect(() => {
        const values = formApi.getValue("B");
        // 检查一下 B 的值在不在 optionList 里面,不在就清除掉
        const newValues = ... // 想办法处理出来
        if(!isEqual(values, newValues)){
            form.setValue("B", newValues);
        }
    }, [AValue]);
    
    return <Form.Select
        field='B'
        optionList={BOptionList}
    />
}

使用 inlineHook 之后,大概就是这样。这并不是说这是什么好办法,这是一个偏好问题,但至少,不需要一个新的组件(当你需要频繁修改的时候,过多的组件跳转会影响编码效率),组件的使用与联动关系可以放得近。

tsx 复制代码
return <Form>
    <Form.Select
        field='A'
        optionList={['a','b','c']}
    />
    {* 可能离得很远 *}
    
    {inlineHook(()=>{
        const AValue = useFieldValue("A");
        const formApi = useFormApi();
        
        useEffect(() => {
            const BOptionList = getBOptionList(v);
            const values = formApi.getValue("B");
            // 检查一下 B 的值在不在 optionList 里面,不在就清除掉
            const newValues = ... // 想办法处理出来
            if(!isEqual(values, newValues)){
                form.setValue("B", newValues);
            }
        }, [AValue]);

        return <Form.Select
            field='B'
            optionList={[...]} // 想办法映射一下选项
        />
    }}
</Form>

用法

tsx 复制代码
return <>
    {* 直接嵌入 *}
    {inlineHook(()=>{
        useState();
        useEffect(()=>{}, []);
        return <>可以返回elem渲染</>
    })}
    {* 条件hook *}
    {
        condition && inlineHook(()=>{
            useEffect(()=>{},[])
            return <>可以返回elem渲染</>
        })
    }
    {* 列表 *}
    {
        list.map(item=>
            inlineHook(item.key, ()=>{
                useState();
                useEffect(()=>{}, []);
                return <>可以返回elem渲染</>;
            }))
    }
</>

好处

  1. 减少一次性 Wrapper 组件
  2. 在编码早期,频繁修改代码的时候,可以尽快验证,而不需要创建一个新的组件

坏处

  1. 你需要绕过这个文件的 react-hook eslint 检查,破坏了规范
  2. 代码会越写越长,人会越来越懒(用爽了都不想重构了)

源码

tsx 复制代码
import React, { createElement, isValidElement } from 'react';

function Hook({ fn }: { fn: () => any }) {
  const res = fn();

  return isValidElement(res) ? res : null;
}

const NullComponent = () => null;
/**
 * 挂载 hook,当你不想为它写一个组件或者不知挂哪个组件上的时候
 * @param fn 传入一个 hook 函数
 * @returns
 */
export function inlineHook(fn: () => any): React.ReactElement;
export function inlineHook(key: string, fn: () => any): React.ReactElement;
export function inlineHook(...args: any[]) {
  const [a, b] = args;
  if (typeof a === 'function') {
    return createElement(Hook, {
      fn: a,
    });
  } else if (typeof a === 'string') {
    return createElement(Hook, {
      key: a,
      fn: b,
    });
  } else {
    return createElement(NullComponent, {});
  }
}
相关推荐
Alair‎4 小时前
200React-Query基础
前端·react.js·前端框架
Alair‎4 小时前
201React-Query:useQuery基本使用
前端·react.js
神秘的猪头4 小时前
# Vue项目初识:从零开始搭建你的第一个现代前端工程化Vue3项目
前端·vue.js·面试
北辰alk4 小时前
React状态提升:为什么它是你项目架构的救星?
react.js
前端西瓜哥4 小时前
Suika图形编辑器的文字支持手动换行了
前端
Можно4 小时前
ES6 Map 全面解析:从基础到实战的进阶指南
前端·javascript·html
黄老五4 小时前
createContext
前端·javascript·vue.js
Mintopia4 小时前
🏗️ React 应用的主题化 CSS 架构方案
前端·react.js·架构
前端无涯4 小时前
Qoder的使用
前端·ide·ai·qoder