You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在React中用Render Props实现类似Tailwind Switch的状态暴露

如何实现带Render Props的Switch组件并暴露内部状态

核心逻辑:Render Props的本质

Render Props模式的核心是让组件的children接收一个函数,组件内部将自身的状态、回调等数据作为参数传给这个函数,从而实现内部状态向外部的暴露。

第一步:从零实现基础版Switch

先写一个最简单的Switch,内部维护状态并通过Render Props暴露:

import { useState } from 'react';

function BasicSwitch({ children }) {
  // 内部维护checked状态
  const [checked, setChecked] = useState(false);

  // 状态切换回调
  const handleToggle = () => {
    setChecked(!checked);
  };

  // 调用children函数,传入需要暴露的状态和回调
  return children({ checked, onChange: handleToggle });
}

// 使用方式
function MyToggle() {
  return (
    <BasicSwitch>
      {({ checked, onChange }) => (
        <button 
          onClick={onChange}
          className={`${checked ? 'bg-blue-600' : 'bg-gray-200'} relative inline-flex h-6 w-11 items-center rounded-full`}
        >
          <span className="sr-only">切换状态</span>
          <span 
            className={`${checked ? 'translate-x-6' : 'translate-x-1'} inline-block h-4 w-4 transform rounded-full bg-white transition`}
          />
        </button>
      )}
    </BasicSwitch>
  );
}

第二步:支持受控/非受控双模式

Tailwind的Switch同时支持外部控制状态(受控模式)和内部维护状态(非受控模式),这是通过useControllable钩子实现的。我们可以自己实现一个简易版:

function useControllable(controlledValue, onChange, defaultValue) {
  // 非受控模式下的内部状态
  const [internalValue, setInternalValue] = useState(defaultValue);
  
  // 判断是否为受控模式:外部传入了controlledValue
  const isControlled = controlledValue !== undefined;
  
  // 当前值:受控用外部值,非受控用内部值
  const currentValue = isControlled ? controlledValue : internalValue;
  
  // 统一的变更处理函数
  const handleChange = (newValue) => {
    // 优先调用外部传入的onChange
    onChange?.(newValue);
    // 非受控模式下更新内部状态
    if (!isControlled) {
      setInternalValue(newValue);
    }
  };

  return [currentValue, handleChange];
}

把这个钩子整合到Switch组件中,就可以同时支持两种使用方式:

function Switch({ 
  checked: controlledChecked, 
  onChange: controlledOnChange, 
  defaultChecked = false,
  children 
}) {
  const [checked, onChange] = useControllable(
    controlledChecked, 
    controlledOnChange, 
    defaultChecked
  );

  return children({ checked, onChange });
}

// 非受控用法(内部维护状态)
<Switch>
  {({ checked }) => /* 自定义UI */}
</Switch>

// 受控用法(外部控制状态)
const [enabled, setEnabled] = useState(false);
<Switch checked={enabled} onChange={setEnabled}>
  {({ checked }) => /* 自定义UI */}
</Switch>

第三步:对应Tailwind源码的核心逻辑

Tailwind的源码实现只是在基础逻辑上做了通用封装:

  1. useControllable:和我们实现的逻辑一致,统一处理受控/非受控状态,拿到最终的checked和onChange。
  2. slot = useMemo(() => ({ checked }), [checked]):将需要暴露的状态缓存,避免不必要的重渲染。
  3. render(...)函数:这是一个通用渲染工具,负责处理用户传入的as属性(比如as={Fragment})、合并组件内部props和用户props,最终调用用户传入的children函数并传入slot中的状态——本质和我们基础版里的children({ checked })完全一致,只是把通用逻辑抽离出来复用。

完整复刻Tailwind式Switch(带隐藏原生input)

如果需要支持表单提交等场景,可以加上隐藏的原生checkbox:

import { useState } from 'react';

function useControllable(controlledValue, onChange, defaultValue) {
  const [internalValue, setInternalValue] = useState(defaultValue);
  const isControlled = controlledValue !== undefined;
  const currentValue = isControlled ? controlledValue : internalValue;
  
  const handleChange = (newValue) => {
    onChange?.(newValue);
    if (!isControlled) setInternalValue(newValue);
  };

  return [currentValue, handleChange];
}

function Switch({ 
  checked: controlledChecked, 
  onChange: controlledOnChange, 
  defaultChecked = false,
  name,
  as = 'div',
  children 
}) {
  const [checked, onChange] = useControllable(controlledChecked, controlledOnChange, defaultChecked);
  const Tag = as;

  const handleToggle = () => {
    onChange(!checked);
  };

  return (
    <>
      {/* 隐藏的原生checkbox,用于表单提交和无障碍支持 */}
      {name && (
        <input
          type="checkbox"
          hidden
          checked={checked}
          name={name}
          onChange={() => {}}
        />
      )}
      {/* 渲染用户自定义UI,传入checked状态 */}
      <Tag onClick={handleToggle}>
        {children({ checked })}
      </Tag>
    </>
  );
}

内容的提问来源于stack exchange,提问作者Tommy Wolfheart

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.03 19:30:01