组件设计 —— 重新认识受控与非受控组件

image

重新定义受控与非受控组件的边界

image

React 官网中对非受控组件与受控组件作了如图中下划线的边界定义。一经推敲, 该定义是缺乏了些完整性严谨性的, 比如针对非表单组件(弹框、轮播图)如何划分受控与非受控的边界? 又比如非受控组件是否真的如文案上所说的数据的展示与变更都由 dom 自身接管呢?

在非受控组件中, 通常业务调用方只需传入一个初始默认值便可使用该组件。以 Input 组件为例:

// 组件提供方
function Input({ defaultValue }) {
  return <input defaultValue={defaultValue} />
}

// 调用方
function Demo() {
  return <Input defaultValue={1} />
}

在受控组件中, 数值的展示与变更则分别由组件的 statesetState 接管。同样以 Input 组件为例:

// 组件提供方
function Input() {
  const [value, setValue] = React.useState(1)
  return <input value={value} onChange={e => setValue(e.target.value)} />
}

// 调用方
function Demo() {
  return <Input />
}

有意思的一个问题来了, Input 组件到底是受控的还是非受控的? 我们甚至还可以对代码稍加改动成 <Input defaultValue={1} /> 的最初调用方式:

// 组件提供方
function Input({ defaultValue }) {
  const [value, setValue] = React.useState(defaultValue)
  return <input value={value} onChange={e => setValue(e.target.value)} />
}

// 调用方
function Demo() {
  return <Input defaultValue={1} />
}

尽管此时 Input 组件本身是一个受控组件, 但与之相对的调用方失去了更改 Input 组件值的控制权, 所以对调用方而言, Input 组件是一个非受控组件。值得一提的是, 以非受控组件的使用方式去调用受控组件是一种反模式, 在下文中会分析其中的弊端。

如何做到不管对于组件提供方还是调用方 Input 组件都为受控组件呢? 提供方让出控制权即可, 调整代码如下codesandbox:

// 组件提供方
function Input({ value, onChange }) {
  return <input value={value} onChange={onChange} />
}

// 调用方
function Demo() {
  const [value, setValue] = React.useState(1)
  return <Input value={value} onChange={e => setValue(e.target.value)} />
}

经过上述代码的推演后, 概括如下: 受控以及非受控组件的边界划分取决于当前组件对于子组件值的变更是否拥有控制权。如若有则该子组件是当前组件的受控组件; 如若没有则该子组件是当前组件的非受控组件。

职能范围

基于调用方对于受控组件拥有控制权这一认知, 因此受控组件相较非受控组件能赋予调用方更多的定制化职能。这一思路与软件开发中的开放/封闭原则有异曲同工之妙, 同时让笔者受益匪浅的 Inversion of Control 也是类似的思想。

借助受控组件的赋能, 以 Input 组件为例, 比如调用方可以更为自由地对值进行校验限制, 又比如在值发生变更时执行一些额外逻辑。

// 组件提供方
function Input({ value, onChange }) {
  return <input value={value} onChange={onChange} />
}

// 调用方
function Demo() {
  const [value, setValue] = React.useState(1)
  return <Input value={value} onChange={e =>
    // 只支持数值的变更
    if (/\D/.test(e.target.value)) return
    setValue(e.target.value)}
  />
}

因此综合基础组件扩展性通用性的考虑, 受控组件的职能相较非受控组件更加宽泛, 建议优先使用受控组件来构建基础组件。

反模式 —— 以非受控组件的使用方式调用受控组件

首先何谓反模式? 笔者将其总结为增大隐性 bug 出现概率的模式, 该模式是最佳实践的对立经验。如若使用了反模式就不得不花更多的精力去避免潜在 bug。官网对反模式也有很好的概括总结

缘何上文提到以非受控组件的使用方式去调用受控组件是一种反模式? 观察 Input 组件的第一行代码, 其将 defaultValue 赋值给 value, 这种将 props 赋值给 state 的赋值行为在一定程度上会增加某些隐性 bug 的出现概率。

比如在切换导航栏的场景中, 恰巧两个导航中传进组件的 defaultValue 是相同的值, 在导航切换的过程中便会将导航一中的 Input 的状态值带到导航二中, 这显然会让使用方感到困惑。codesandbox

// 组件提供方
function Input({ defaultValue }) {
  // 反模式
  const [value, setValue] = React.useState(defaultValue);
  React.useEffect(() => {
    setValue(defaultValue);
  }, [defaultValue]);
  return <input value={value} onChange={e => setValue(e.target.value)} />;
}

// 调用方
function Demo({ defaultValue }) {
  return <Input defaultValue={defaultValue} />;
}

function App() {
  const [tab, setTab] = React.useState(1);
  return (
    <>
      {tab === 1 ? <Demo defaultValue={1} /> : <Demo defaultValue={1} />}
      <button onClick={() => (tab === 1 ? setTab(2) : setTab(1))}>
        切换 Tab
      </button>
    </>
  );
}

如何避免使用该反模式同时有效解决问题呢? 官方提供了两种较为优质的解法, 将其留给大家作为思考。

  1. 方法一: 使用完全受控组件(更为推荐)
  2. 方法二: 使用完全非受控组件 + key

欢迎关注 personal blog

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 211,884评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,347评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,435评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,509评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,611评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,837评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,987评论 3 408
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,730评论 0 267
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,194评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,525评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,664评论 1 340
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,334评论 4 330
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,944评论 3 313
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,764评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,997评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,389评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,554评论 2 349

推荐阅读更多精彩内容