原文链接:React: hybrid controlled components in action

FBI WARNING: 对于提倡无状态设计的React来说这可能是一种反模式。

众所周知,有很多web组件可以通过用户交互改变它的状态,如<input><select>,或者我们常用的一些在线富文本编辑器。这些组件在日常开发中不是很起眼 - 我们可以通过在其中键入内容或设置value属性来轻松修改它的值。但是,由于React是单向数据绑定的,在React中使用这些组件不是很好控制它的状态:

1.一个维护自身Stateinput组件不能从外部修改它的状态;
2.一个input组件的值如果由外部props传入,则其值受外部控制;

基于上述两个特点,React提出了受控组件非受控组件的概念。

受控组件

一个受控的input组件接受一个value属性,渲染一个<input>元素,其值反应value属性的值。

受控组件不保存其自身的内部状态; 该组件纯粹根据props呈现内容。

也就是说,如果我们有一个通过props设置valueinput组件,它将持续显示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.valuestate.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.valuestate.value。但props.value的值在渲染时具有更高的优先级,state.value反映了组件的真实值。

最新文章

  1. win7安装oracle11g64位提示环境变量Path长度超出
  2. 给 IIS Express 配置虚拟目录
  3. easyUi学习备忘
  4. go语言入门
  5. UVA- 1504 - Genghis Khan the Conqueror(最小生成树-好题)
  6. 统计建模与R软件习题二答案
  7. 用Jfree实现条形柱状图表,java代码实现
  8. centos jdk 安装
  9. go 监听系统信号
  10. 第21月第9日 windows下使用vim+ctags+taglist
  11. vue--音乐播放器
  12. PAM unable to dlopen(/lib/security/pam_limits.so): /lib/security/pam_limits.so: wrong ELF class: ELFCLASS32
  13. linux下service+命令和直接去执行命令的区别,怎么自己建立一个service启动
  14. C语言进度条实现。(转)
  15. C++/Php/Python 语言执行shell命令
  16. FastText 文本分类使用心得
  17. 怎么安装预装的win8三星笔记本改win7再装Ubuntu问题[zz]
  18. 前端Table数据导出Excel使用HSSFWorkbook(Java)
  19. mysql5.7.20多实例编译安装
  20. angular5给懒加载模块添加loading

热门文章

  1. 使用diff或者vimdiff比较远程文件(夹)与本地文件夹
  2. nuxt npm run dev 报错Solution to the &quot;Error: listen EADDRINUSE 127.0.0.1:8080&quot;
  3. CF_528D
  4. head里两个重要标签base和meta
  5. 【sql server】备份集中的数据库与现有数据库不同 解决方案
  6. JMeter二次开发环境配置
  7. ArcGis dbf读写——挂接Excel到属性表 C#
  8. Python——Selenium &amp; Chrome Driver配置
  9. ASP.NET MVC上传文件
  10. important的妙用解决firefox和ie的css兼容问题