受控组件与非受控组件在官网与国内网上的资料都不多,有些人觉得它可有可不有,也不在意。这恰恰显示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”时,这是由用户还是框架修改的。
if (!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) "_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("div"); } const node = ReactDOM.render(component, container); node.defaultValue = node.innerHTML.replace(/^\n/, ""); return node; }; const container = document.createElement("div"); //注意这个方法,用户在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");
纯文本类: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之流则可能遇坑。
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持。
免责声明:本站资源来自互联网收集,仅供用于学习和交流,请遵循相关法律法规,本站一切资源不代表本站立场,如有侵权、后门、不妥请联系本站删除!
《魔兽世界》大逃杀!60人新游玩模式《强袭风暴》3月21日上线
暴雪近日发布了《魔兽世界》10.2.6 更新内容,新游玩模式《强袭风暴》即将于3月21 日在亚服上线,届时玩家将前往阿拉希高地展开一场 60 人大逃杀对战。
艾泽拉斯的冒险者已经征服了艾泽拉斯的大地及遥远的彼岸。他们在对抗世界上最致命的敌人时展现出过人的手腕,并且成功阻止终结宇宙等级的威胁。当他们在为即将于《魔兽世界》资料片《地心之战》中来袭的萨拉塔斯势力做战斗准备时,他们还需要在熟悉的阿拉希高地面对一个全新的敌人──那就是彼此。在《巨龙崛起》10.2.6 更新的《强袭风暴》中,玩家将会进入一个全新的海盗主题大逃杀式限时活动,其中包含极高的风险和史诗级的奖励。
《强袭风暴》不是普通的战场,作为一个独立于主游戏之外的活动,玩家可以用大逃杀的风格来体验《魔兽世界》,不分职业、不分装备(除了你在赛局中捡到的),光是技巧和战略的强弱之分就能决定出谁才是能坚持到最后的赢家。本次活动将会开放单人和双人模式,玩家在加入海盗主题的预赛大厅区域前,可以从强袭风暴角色画面新增好友。游玩游戏将可以累计名望轨迹,《巨龙崛起》和《魔兽世界:巫妖王之怒 经典版》的玩家都可以获得奖励。
更新日志
- 小骆驼-《草原狼2(蓝光CD)》[原抓WAV+CUE]
- 群星《欢迎来到我身边 电影原声专辑》[320K/MP3][105.02MB]
- 群星《欢迎来到我身边 电影原声专辑》[FLAC/分轨][480.9MB]
- 雷婷《梦里蓝天HQⅡ》 2023头版限量编号低速原抓[WAV+CUE][463M]
- 群星《2024好听新歌42》AI调整音效【WAV分轨】
- 王思雨-《思念陪着鸿雁飞》WAV
- 王思雨《喜马拉雅HQ》头版限量编号[WAV+CUE]
- 李健《无时无刻》[WAV+CUE][590M]
- 陈奕迅《酝酿》[WAV分轨][502M]
- 卓依婷《化蝶》2CD[WAV+CUE][1.1G]
- 群星《吉他王(黑胶CD)》[WAV+CUE]
- 齐秦《穿乐(穿越)》[WAV+CUE]
- 发烧珍品《数位CD音响测试-动向效果(九)》【WAV+CUE】
- 邝美云《邝美云精装歌集》[DSF][1.6G]
- 吕方《爱一回伤一回》[WAV+CUE][454M]