React:受控组件与非受控组件混用实战 - 译文
原文链接:React: hybrid controlled components in action
FBI WARNING: 对于提倡无状态设计的React来说这可能是一种反模式。
众所周知,有很多web组件可以通过用户交互改变它的状态,如<input>
,<select>
,或者我们常用的一些在线富文本编辑器。这些组件在日常开发中不是很起眼 - 我们可以通过在其中键入内容或设置value
属性来轻松修改它的值。但是,由于React是单向数据绑定的,在React中使用这些组件不是很好控制它的状态:
1.一个维护自身
State
的input
组件不能从外部修改它的状态;
2.一个input
组件的值如果由外部props
传入,则其值受外部控制;
基于上述两个特点,React提出了受控组件和非受控组件的概念。
受控组件
一个受控的
input
组件接受一个value
属性,渲染一个<input>
元素,其值反应value
属性的值。受控组件不保存其自身的内部状态; 该组件纯粹根据
props
呈现内容。
也就是说,如果我们有一个通过props
设置value
的input
组件,它将持续显示props.value
,即使你通过键盘输入字符。换句话说,你的组件是只读的。
很多流行的组件都是以这种方式运行。如果我们将类似value
这样的属性从这些组件中移除,你好发现它会变成一个“死亡的组件”, - 谁会爱死一个死人呢?
在使用受控组件时,您必须始终传递一个value
属性,同时注册一个onChange
处理程序,才能以使它们处于活动状态,如此一来,它的上层组件会变的复杂和混乱。
非受控组件
一个不带
value
属性的input
组件是非受控组件。用户的任何输入都将立即被反应在渲染元素上。不受控制的组件保持其自身的内部状态。
这样的组件运作起来更像原生的组件。可是等等!我们如何像以前那样通过普通的js操作input.value = xxx
来更改输入值呢?
遗憾的是,你没有办法从外部改变其内部的状态,因为它是不受控制。
混用受控组件和非受控组件
那么为什么不构建一个既受控又不受控制的组件呢?根据React对(非)受控组件的定义,我们可以得到一些启示和原则:
原则一
props.value
始终具有比内部state.value
更高的优先级。
当设置了props.value
,我们应该始终使用其值代替state.value
渲染组件,所以我们可以定义一个displayValue
getter属性:
get displayValue() {
return this.props[key] !== undefined ?
this.props[key] : this.state[internalKey];
}
然后在render
功能:
render() {
return (<div>{this.displayValue}</div>);
}
原则二
组件的任何更改都应同步到内部
state.value
,然后通过props.onChange
请求更新上层组件的状态。
将值同步到state.value
可以确保组件在不受控制时能够呈现最新值。请求外部更新告诉上层组件执行更改props.value
,因此受控组件也可以呈现正确的值。
handleChange(newVal) {
if (newVal === this.state.value) return;
this.setState({
value: newVal,
}, () => {
this.props.onChange && this.props.onChange(newVal);
});
}
原则三
当组件接收到新的
props
时将props.value
映射到state.value
同步props.value
和state.value
的值是非常关键的,它能及时修正内部状态并保证handleChange
的正确运转。
componentWillReceiveProps(nextProps) {
const controlledValue = nextProps.value;
if (controlledValue !== undefined &&
controlledValue !== this.state.value
) {
this.setState({
value: controlledValue,
});
}
}
原则四
确保优先的值发生变化才更新组件。
这可以防止组件进行不必要的重新渲染,例如,受控组件在内部state.value
更改时不应触发重新渲染。
shouldComponentUpdate(nextProps, nextState) {
if (nextProps.value !== undefined) {
// controlled, use `props.value`
return nextProps.value !== this.props.value;
}
// uncontrolled, use `state.value`
return nextState.value !== this.state.value;
}
实施方案
综上所有原则,我们可以创建一个装饰器如下:
/**
* Optimize hybrid controlled component by add some method into proto
*
* Usage:
* @hybridCtrl
* class App extends React.Component {
* ...
* }
*
* @hybridCtrl('specified_prop_to_assign')
* class App extends React.Component {
* ...
* }
*
* @hybridCtrl('specified_prop_to_assign', '_internal_prop')
* class App extends React.Component {
* ...
* }
*/
import shallowCompare from 'react-addons-shallow-compare';
const noop = () => {};
const optimizer = (Component, key = 'value', internalKey = `_${key}`) => {
// need `this`
function shallowCompareWithExcept(nextProps, nextState) {
const props = {
...nextProps,
[key]: this.props[key], // patched with same value
};
const state = {
...nextState,
[internalKey]: this.state[internalKey],
};
return shallowCompare(this, props, state);
}
const {
shouldComponentUpdate = shallowCompareWithExcept,
componentWillReceiveProps = noop,
} = Component.prototype;
Object.defineProperty(Component.prototype, 'displayValue', {
get: function getDisplayValue() {
// prefer to use `props[key]`
return this.props[key] !== undefined ?
this.props[key] : this.state[internalKey];
},
});
// assign new props to state
Object.defineProperty(Component.prototype, 'componentWillReceiveProps', {
configurable: false,
enumerable: false,
writable: true,
value: function componentWillReceivePropsWrapped(nextProps) {
const controlledValue = nextProps[key];
if (controlledValue !== undefined &&
controlledValue !== this.state[internalKey]
) {
this.setState({
[internalKey]: this.mapPropToState ?
this.mapPropToState(controlledValue) : controlledValue,
});
}
componentWillReceiveProps.call(this, nextProps);
},
});
// patch shouldComponentUpdate
Object.defineProperty(Component.prototype, 'shouldComponentUpdate', {
configurable: false,
enumerable: false,
writable: true,
value: function shouldComponentUpdateWrapped(nextProps, nextState) {
let result = true;
if (nextProps[key] !== undefined) {
// controlled, use `props[key]`
result &= (nextProps[key] !== this.props[key]);
} else {
// uncontrolled, use `state[internalKey]`
result &= (nextState[internalKey] !== this.state[internalKey]);
}
// logic OR rocks
return result ||
shouldComponentUpdate.call(this, nextProps, nextState);
},
});
return Component;
};
export const hybridCtrl = (keyOrComp, internalKey) => {
if (typeof keyOrComp === 'function') {
return optimizer(keyOrComp);
}
return (Component) => optimizer(Component, keyOrComp, internalKey);
};
这个装饰器的使用方法如下:
import PropTypes from 'prop-types'
@hybridCtrl
class App extends React.Component {
static propTypes = {
value: PropTypes.any,
}
state = {
_value: '',
}
mapPropToState(controlledValue) {
// your can do some transformations from `props.value` to `state._value`
}
handleChange(newVal) {
// it's your duty to handle change events and dispatch `props.onChange`
}
}
总结
1.我们为什么需要混合受控组件和非受控组件?(什么场合需要使用杂交组件?)
我们需要创建同时受控和非受控制的组件,就像原生组件一样。
2.混合的主要思想是什么?
同时维护props.value
和state.value
。但props.value
的值在渲染时具有更高的优先级,state.value
反映了组件的真实值。
React:受控组件与非受控组件混用实战 - 译文的更多相关文章
- 浅谈react受控组件与非受控组件
引言 最近在使用蚂蚁金服出品的一条基于react的ant-design UI组件时遇到一个问题,编辑页面时input输入框会展示保存前的数据,但是是用defaultValue就是不起作用,输入框始终为 ...
- React受控组件和非受控组件
受控组件和非受控组件主要是用来解决表单组件状态谁来控制的问题.因为用户的输入会反应在界面上,相当于视图的状态发生了变化,而react是通过虚拟DOM比对修改视图的,这里就要决定谁来控制表单组件的状态. ...
- react 表单(受控组件和非受控组件)
我们知道表单元素与其他的普通DOM元素来说是不一样的,它们保存了自己的一些状态. 我们主要说的就是表单元素中的受控组件和非受控组件. 受控组件就是这个组件的状态是我们(react)控制的,这个组件的行 ...
- react中 受控组件和 非受控组件 浅析
一 受控组件 顾名思义,受控 也就是能够被控制,简而言之也就是 该组件ui的显示或者内部state逻辑的变化依赖外部的 props的传入. 二 非受控组件 顾名思义,非受控,也就是内部的视图变化,st ...
- react第十一单元(受控组件和非受控组件-实现类似于vue双向绑定的功能)
第十一单元(受控组件和非受控组件-实现类似于vue双向绑定的功能) #课程目标 理解因为react的单向数据流 理解表单组件会因为react数据流变的不好维护 理解受控组件与非受控组件的实质区别 理解 ...
- Vue父子组件及非父子组件如何通信
1.父组件传递数据给子组件 父组件数据如何传递给子组件呢?可以通过props属性来实现 父组件: 子组件通过props来接收数据: 方式1: 方式2 : 方式3: 这样呢,就实现了父组件向子组件传递数 ...
- 学习React系列(四)——受控组件与非受控组件
受控组件:通过组件的状态与属性的改变来控制组件 不可控组件:直接通过底层的dom来控制组件(具体来说就是通过绑定再底层dom上的方法来实现的,比如说ref,onChange) 受控组件 functio ...
- React 受控组件和非受控组件
需求用户名自动获取 onChange用户状态发生改变 就获取值 就是时时获取值 使用onChange 点击按钮 获取密码 只要绑定了点击事件 就可以获取值 通过 let usercont=event. ...
- React组件之间通过Props传值的技巧(小案例,帮助体会理解props、state、受控组件和非受控组件等)
本文重要是根据react小书上的一个很简单的例子改编的,加上自己的学习理解,希望可以通过实际案例让大家对概念有更清晰的理解,当然也希望能一块学习. import React,{Component} f ...
随机推荐
- KDJ计算公式
计算方法编辑KDJ的计算比较复杂,首先要计算周期(n日.n周等)的RSV值,即未成熟随机指标值,然后再计算K值.D值.J值等.以n日KDJ数值的计算为例,其计算公式为n日RSV=(Cn-Ln)/(Hn ...
- 快速理解js中的call,apply的作用
今天被人问到js中的call,apply的区别和用途,解释了一番后,想到之前在逼乎上看到一位小伙伴生动形象的解释 本身不难理解,看下MDN就知道了,但是不常用,遇到了,还要脑回路回转下.或者时间长了, ...
- App测试全(转自鲁德)
1.App测试流程 1.1流程图 1.2测试周期 测试周期可按项目的开发周期来确定测试时间,一般测试时间为两三周(即15个工作日),根据项目情况以及版本质量可适当缩短或延长测试时间. 1.3测试资源 ...
- 基于Redis的分布式锁真的安全吗?
说明: 我前段时间写了一篇用consul实现分布式锁,感觉理解的也不是很好,直到我看到了这2篇写分布式锁的讨论,真的是很佩服作者严谨的态度, 把这种分布式锁研究的这么透彻,作者这种技术态度真的值得我好 ...
- 对filter的初步认识
Filter 技术是servlet 2.3 新增加的功能.servlet2.3是sun公司 于2000年10月发布的,它的开发者包括许多个人和公司团体,充分体现了sun公司所倡导的代码开放性原则.在 ...
- MarkDown里面的Emoji表情
我才发现MarkDown里面可以使用一些Emoji表情,好玩,以后写博客的趣味性大大增加 想看全部的就去这里找https://www.webfx.com/tools/emoji-cheat-sheet ...
- JAVA中循环删除list中元素的方法总结【转】
印象中循环删除list中的元素使用for循环的方式是有问题的,但是可以使用增强的for循环,然后今天在使用时发现报错了,然后去科普了一下,再然后发现这是一个误区.下面就来讲一讲..伸手党可直接跳至文末 ...
- C51学习
十六个数字循环显示 #include<reg52.h>#include<intrins.h>#define uint unsigned int#define uchar uns ...
- 移动文件(git mv)
使用git mv命令将mian.c移动为main2.c $ git mv main.c main2.c D:\Git\test (master -> origin) $ git status O ...
- SpringMVC+Apache Shiro+JPA(hibernate)整合配置
序: 关于标题: 说是教学,实在愧不敢当,但苦与本人文笔有限,实在找不到更合理,谦逊的词语表达,只能先这样定义了. 其实最真实的想法,只是希望这个关键词能让更多的人浏览到这篇文章,也算是对于自己写文章 ...