React深度编程:受控组件与非受控组件

时间: 2018-01-08阅读: 653标签: React
原文出处: 司徒正美     

受控组件与非受控组件在官网与国内网上的资料都不多,有些人觉得它可有可不有,也不在意。这恰恰显示React的威力,满足不同规模大小的工程需求。譬如你只是做ListView这样简单的数据显示,将数据拍出来,那么for循坏与{}就足够了,但后台系统存在大量报表,不同的表单联动,缺了受控组件真的不行。

受控组件与非受控组件是React处理表单的入口。从React的思路来讲,作者肯定让数据控制一切,或者简单的理解为,页面的生成与更新得忠实地执行JSX的指令。

但是表单元素有其特殊之处,用户可以通过键盘输入与鼠标选择,改变界面的显示。界面的改变也意味着有一些数据被改动,比较明显的是input的value,textarea的innerHTML,radio/checkbox的checked,不太明显的是option的selectedselectedIndex,这两个是被动修改的。

<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("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之流则可能遇坑。

为什么说React 16是开发者的福音?

就像人们对更新移动应用程序和操作系统感到兴奋一样,开发人员也应该对更新框架感到兴奋。不同框架的新版本具有新特性和开箱即用的技巧。下面是将现有应用程序从 React 15 迁移到 React 16 时应该考虑的一些好特性。

React ref

React ref理解:通过指定ref获得你想操作的元素,然后进行修改.是当组件挂载后和卸载后,以及ref属性本身发生变化时,回调函数就会被调用。 因为ref引用的是组件的实例

函数式的React

React 是现在最流行的 JavaScript 库之一。使用 React 可以非常轻松地创建 Web 用户交互界面。 它的成功有很多因素,但也许其中一个因素是清晰有效的编程方法。在 React 的世界中,UI 是由一个一个组件所组成的。

react hook超实用的用法和技巧分析

react hook发布也已经有几个月了,相信有部分人已经开始使用了,还有些人在犹豫要不要用,可能更多人安于现状,没有要用的打算,甚至还有很多公司的react版本是15或以下的,迫于升级的难度没有使用。以我个人的观点,要不要使用react hook呢?

react中PureComponent浅对比策略

PureComponent实现了Component中没有实现的shouComponentUpdata()方法,会对state和props进行一次浅对比,本文介绍一下浅对比策略,源码中,实现浅对比的函数是:shallowEqual()

如何扩展 Create React App 的 Webpack 配置

Create React App(以下简称 CRA)是创建 React 应用的一个脚手架,它与其他脚手架不同的一个地方就是将一些复杂工具(比如 webpack)的配置封装了起来,让使用者不用关心这些工具的具体配置,从而降低了工具的使用难度。

渐进式React

可以说 React 为Web开发者带来了全新的开发模式,而在各类新功能下,如何达到性能最优仍是我们需要关心的。今天做一次精读尝试,原文地址在文末,话不多说,先呈上一份性能清单:

必须要会的 50 个React 面试题

如果你是一位有抱负的前端程序员并准备面试,那么这篇文章很适合你。本文是你学习和面试 React 所需知识的完美指南。JavaScript 工具缓慢而稳定地在市场中扎根,对 React 的需求呈指数级增长。选择合适的技术来开发应用或网站变得越来越有挑战性

react-navigation 监听顶部导航栏点击/滑动状态

使用createMaterialTopTabNavigator创建顶部导航栏,希望实现切换到指定的Tab再获取数据,查看官方文档只能找到tabBarOnPress 方法监听点击回调,但是无法监听滑动切换

解决vscode 开发react 导入绝对路径 无法跳转的问题

相对路径可正常跳转,但是在webpack配置alias使用绝对路径后无法跳转.解决办法:需要添加一个jsconfig文件,如下:

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

广告赞助文章投稿关于web前端网站点搜索站长推荐网站地图站长QQ:522607023

小程序专栏: 土味情话心理测试脑筋急转弯幽默笑话段子句子语录成语大全