이 글에서는 주로 React의 Controlled Component와 UnControlled Component에 대해 자세히 설명하고 있습니다. 공식 홈페이지와 국내 웹사이트에는 Controlled Component와 UnControlled Component에 대한 정보가 많지 않습니다. 상관없어. 이는 다양한 규모의 프로젝트 요구 사항을 충족하는 React의 힘을 보여줍니다. 예를 들어 ListView와 같은 간단한 데이터 표시를 수행하고 데이터를 캡처하는 경우에는 for 루프와 {}
로 충분합니다. 그러나 백그라운드 시스템에는 많은 수의 보고서가 있으며 서로 다른 양식이 서로 연결되어 있어 구성 요소가 실제로 작동하지 않습니다. {}
就足够了,但后台系统存在大量报表,不同的表单联动,缺了受控组件真的不行。
受控组件与非受控组件是React处理表单的入口。从React的思路来讲,作者肯定让数据控制一切,或者简单的理解为,页面的生成与更新得忠实地执行JSX的指令。
但是表单元素有其特殊之处,用户可以通过键盘输入与鼠标选择,改变界面的显示。界面的改变也意味着有一些数据被改动,比较明显的是input的value,textarea的innerHTML,radio/checkbox的checked,不太明显的是option的selected与selectedIndex,这两个是被动修改的。
<input value={this.state.value} />
当input.value是由组件的state.value拍出来的,当用户进行输入修改后,然后JSX再次重刷视图,这时input.value是采取用户的新值还是state的新值?基于这个分歧,React给出一个折衷的方案,两者都支持,于是就产生了今天的主题了。
React认为value/checked不能单独存在,需要与onInput/onChange/disabed/readOnly等控制value/checked的属性或事件一起使用。 它们共同构成受控组件,受控是受JSX的控制。如果用户没有写这些额外的属性与事件,那么框架内部会给它添加一些事件,如onClick, onInput, onChange,阻止你进行输入或选择,让你无法修改它的值。在框架内部,有一个顽固的变量,我称之为 persistValue,它一直保持JSX上次赋给它的值,只能让内部事件修改它。
因此我们可以断言,受控组件是可通过事件完成的对value的控制。
在受控组件中,persistValue总能被刷新。
我们再看非受控组件,既然value/checked已经被占用了,React启用了HTML中另一组被忽略的属性defaultValue/defaultChecked。一般认为它们是与value/checked相通的,即,value不存在的情况下,defaultValue的值就当作是value。
上面我们已经说过,表单元素的显示情况是由内部的 persistValue 控制的,因此defaultXXX也会同步persistValue,然后再由persistValue同步DOM。但非受控组件的出发点是忠实于用户操作,如果用户在代码中
input.value = "xxxx"
以后
<input defaultValue={this.state.value} />
就再不生效,一直是xxxx。
它怎么做到这一点,怎么辨识这个修改是来自框架内部或外部呢?我翻看了一下React的源码,原来它有一个叫valueTracker的东西跟踪用户的输入
var tracker = { getValue: function () { return currentValue; }, setValue: function (value) { currentValue = '' + value; }, stopTracking: function () { detachTracker(node); delete node[valueField]; } }; return tracker; }
这个东西又是通过Object.defineProperty打进元素的value/checked的内部,因此就知晓用户对它的取值赋值操作。
但value/checked还是两个很核心的属性,涉及到太多内部机制(比如说value与oninput, onchange, 输入法事件oncompositionstart,
compositionchange, oncompositionend, onpaste, oncut),为了平缓地修改value/checked,
还要用到Object.getOwnPropertyDescriptor
。如果我要兼容IE8,没有这么高级的玩艺儿。我采取另一种更安全的方式,
只用Object.defineProperty修改defaultValue/defaultChecked
。
首先我为元素添加一个_uncontrolled
的属性,用来表示我已经劫持过defaultXXX。 然后描述对象 (Object.defineProperty的第三个参数
)的set方法里面再添加一个开关,_observing
。在框架内部更新视图,此值为false,更新完,它置为true。
这样就知晓 input.defaultValue = "xxx"时,这是由用户还是框架修改的。
f (!dom._uncontrolled) { dom._uncontrolled = true; inputMonitor.observe(dom, name); //重写defaultXXX的setter/getter } dom._observing = false;//此时是框架在修改视图,因此需要关闭开关 dom[name] = val; dom._observing = true;//打开开关,来监听用户的修改行为
inputMonitor的实现如下
export var inputMonitor = {}; var rcheck = /checked|radio/; var describe = { set: function(value) { var controllProp = rcheck.test(this.type) ? "checked" : "value"; if (this.type === "textarea") { this.innerHTML = value; } if (!this._observing) { if (!this._setValue) { //defaultXXX只会同步一次_persistValue var parsedValue = (this[controllProp] = value); this._persistValue = Array.isArray(value) ? value : parsedValue; this._setValue = true; } } else { //如果用户私下改变defaultValue,那么_setValue会被抺掉 this._setValue = value == null ? false : true; } this._defaultValue = value; }, get: function() { return this._defaultValue; }, configurable: true }; inputMonitor.observe = function(dom, name) { try { if ("_persistValue" in dom) { dom._setValue = true; } Object.defineProperty(dom, name, describe); } catch (e) {} };
又不小心贴了这么烧脑的代码,这是码农的坏毛病。不过,到这步,大家都明白,无论是官方react还是anu/qreact都是通过Object.defineProperty来控制用户的输入的。
于是我们可以理解以下的代码的行为了
var a = ReactDOM.render(<textarea defaultValue="foo" />, container); ReactDOM.render(<textarea defaultValue="bar" />, container); ReactDOM.render(<textarea defaultValue="noise" />, container); expect(a.defaultValue).toBe("noise"); expect(a.value).toBe("foo"); expect(a.textContent).toBe("noise"); expect(a.innerHTML).toBe("noise");
由于用户一直没有手动修改 defaultValue,dom._setValue
一直为false/undefined,因此 _persistValue
var renderTextarea = function(component, container) { if (!container) { container = document.createElement("p"); } const node = ReactDOM.render(component, container); node.defaultValue = node.innerHTML.replace(/^\n/, ""); return node; }; const container = document.createElement("p"); //注意这个方法,用户在renderTextarea中手动改变了defaultValue,_setValue就变成true const node = renderTextarea(<textarea defaultValue="giraffe" />, container); expect(node.value).toBe("giraffe"); // _setValue后,gorilla就不能同步到_persistValue,因此还是giraffe renderTextarea(<textarea defaultValue="gorilla" />, container); // expect(node.value).toEqual("giraffe"); node.value = "cat"; // 这个又是什么回事了呢,因此非监控属性是在diffProps中批量处理的,在监控属性,则是在更后的方法中处理 // 检测到node.value !== _persistValue,于是重写 _persistValue = node.value,于是输出cat renderTextarea(<textarea defaultValue="monkey" />, container); expect(node.value).toEqual("cat");
//精确匹配 var dom = ReactDOM.render( <select value={222}> <option value={111}>aaa</option> <option value={"222"}>xxx</option> <option value={222}>bbb</option> <option value={333}>ccc</option> </select>, container ); expect(dom.options[2].selected).toBe(true);//选中第三个
//模糊匹配 var dom = ReactDOM.render( <select value={222}> <option value={111}>aaa</option> <option value={"222"}>xxx</option> <option value={333}>ccc</option> </select>, container ); expect(dom.options[2].selected).toBe(true);//选中第二个
Object.getOwnPropertyDescriptor
도 사용하세요. IE8과 호환되려면 그러한 고급 가젯이 없습니다. 저는 또 다른 안전한 접근 방식을 취하고 defaultValue/defaultChecked
를 수정하기 위해 Object.defineProperty를 사용합니다. 🎜🎜먼저, defaultXXX를 하이재킹했음을 나타내기 위해 요소에 _uncontrolled
속성을 추가합니다. 그런 다음 설명 개체(Object.defineProperty의 세 번째 매개 변수
)의 set 메서드에 또 다른 스위치 _observing
를 추가합니다. 프레임 내에서 뷰가 업데이트되면 이 값은 업데이트 후 true로 설정됩니다. 🎜🎜이 방법을 통해 input.defaultValue = "xxx"가 사용자 또는 프레임워크에 의해 수정되었는지 알 수 있습니다. 🎜rrreee🎜inputMonitor 구현은 다음과 같습니다🎜rrreee🎜코더들의 나쁜 버릇인 뇌를 태우는 코드를 실수로 올렸습니다. 그러나 이 시점에서는 공식적인 반응과 anu/qreact 모두 Object.defineProperty를 통해 사용자 입력을 제어한다는 점을 모두가 이해합니다. 🎜🎜따라서 다음 코드의 동작을 이해할 수 있습니다🎜rrreee🎜사용자가 defaultValue를 수동으로 수정하지 않았기 때문에 dom._setValue
는 항상 false/undefine이었습니다. _persistValue
는 언제든지 수정할 수 있습니다. 🎜🎜또 다른 예: 🎜rrreee🎜물론 양식 요소에는 다양한 유형이 있으며 각 양식 요소에는 기본 동작도 있습니다. 🎜纯文本类:text, textarea, JSX的值,总是往字符串转换
type="number"的控制,值总是为数字,不填或为“”则转换为“0”
radio有联动效果,同一父节点下的相同name的radio控制只能选择一个。
select的value/defaultValue支持数组,不做转换,但用户对底下的option元素做增删操作,selected会跟着变动。
此外select还有模糊匹配与精确匹配之分。
//精确匹配 var dom = ReactDOM.render( <select value={222}> <option value={111}>aaa</option> <option value={"222"}>xxx</option> <option value={222}>bbb</option> <option value={333}>ccc</option> </select>, container ); expect(dom.options[2].selected).toBe(true);//选中第三个
//模糊匹配 var dom = ReactDOM.render( <select value={222}> <option value={111}>aaa</option> <option value={"222"}>xxx</option> <option value={333}>ccc</option> </select>, container ); expect(dom.options[2].selected).toBe(true);//选中第二个
凡此种种,React/anu都是做了大量工作,迷你如preact/react-lite之流则可能遇坑。
相关推荐:
위 내용은 React로 제어되는 구성 요소와 제어되지 않는 구성 요소에 대한 자세한 설명의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!