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

更新日期: 2019-10-23阅读: 1.6k标签: 组件

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

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

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

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

在受控组件中, 数值的展示与变更则分别由组件的 state 与 setState 接管。同样以 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


链接: https://www.fly63.com/article/detial/6589

Vuetify基于vue2.0,为移动而生的组件框架

Vuetify 支持SSR(服务端渲染),SPA(单页应用程序),PWA(渐进式Web应用程序)和标准HTML页面。 Vuetify是一个渐进式的框架,试图推动前端开发发展到一个新的水平。

Vue中插槽的作用_Vue组件插槽的使用以及调用组件内的方法

通过给组件传递参数, 可以让组件变得更加可扩展, 组件内使用props接收参数,slot的使用就像它的名字一样, 在组件内定义一块空间。在组件外, 我们可以往插槽里填入任何元素。slot-scope的作用就是把组件内的数据带出来

react 函数子组件(Function ad Child Component)

函数子组件(FaCC )与高阶组件做的事情很相似, 都是对原来的组件进行了加强,类似装饰者。FaCC,利用了react中children可以是任何元素,包括函数的特性,那么到底是如何进行增强呢?

Vue和React组件之间的传值方式

在现代的三大框架中,其中两个Vue和React框架,组件间传值方式有哪些?组件间的传值是灵活的,可以有多种途径,父子组件同样可以使用EventBus,Vuex或者Redux

vue.js自定义组件directives

自定义指令:以v开头,如:v-mybind。bind的作用是定义一个在绑定时执行一次的初始化动作,观察bind函数,它将指令绑定的DOM作为一个参数,在函数体中,直接操作DOM节点为input赋值。

vue中prop属性传值解析

prop的定义:在没有状态管理机制的时候,prop属性是组件之间主要的通信方式,prop属性其实是一个对象,在这个对象里可以定义一些数据,而这些数据可以通过父组件传递给子组件。 prop属性中可以定义属性的类型,也可以定义属性的初始值。

Web组件简介

Web组件由三个独立的技术组成:自定义元素。很简单,这些是完全有效的HTML元素,包含使用一组JavaScript API制作的自定义模板,行为和标记名称(例如,<one-dialog>)。

web组件调用其他web资源

web组件可以直接或间接的调用其他web资源。一个web组件通过内嵌返回客户端内容的另一个web资源的url来间接调用其他web资源。在执行时,一个web资源通过包含另一个资源的内容或者转发请求到另一个资源直接调用。

vue中如何实现的自定义按钮

在实际开发项目中,有时我们会用到自定义按钮;因为一个项目中,众多的页面,为了统一风格,我们会重复用到很多相同或相似的按钮,这时候,自定义按钮组件就派上了大用场,我们把定义好的按钮组件导出,在全局引用,就可以在其他组件随意使用啦,这样可以大幅度的提高我们的工作效率。

Vue子组件调用父组件的方法

Vue中子组件调用父组件的方法,这里有三种方法提供参考,第一种方法是直接在子组件中通过this.$parent.event来调用父组件的方法,第二种方法是在子组件里用$emit向父组件触发一个事件,父组件监听这个事件就行了。

点击更多...

内容以共享、参考、研究为目的,不存在任何商业目的。其版权属原作者所有,如有侵权或违规,请与小编联系!情况属实本人将予以删除!