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, {});
  }
}
相关推荐
崔庆才丨静觅1 天前
hCaptcha 验证码图像识别 API 对接教程
前端
passerby60611 天前
完成前端时间处理的另一块版图
前端·github·web components
掘了1 天前
「2025 年终总结」在所有失去的人中,我最怀念我自己
前端·后端·年终总结
崔庆才丨静觅1 天前
实用免费的 Short URL 短链接 API 对接说明
前端
崔庆才丨静觅1 天前
5分钟快速搭建 AI 平台并用它赚钱!
前端
崔庆才丨静觅1 天前
比官方便宜一半以上!Midjourney API 申请及使用
前端
Moment1 天前
富文本编辑器在 AI 时代为什么这么受欢迎
前端·javascript·后端
崔庆才丨静觅1 天前
刷屏全网的“nano-banana”API接入指南!0.1元/张量产高清创意图,开发者必藏
前端
剪刀石头布啊1 天前
jwt介绍
前端
爱敲代码的小鱼1 天前
AJAX(异步交互的技术来实现从服务端中获取数据):
前端·javascript·ajax