## 受控组件与非受控组件


受控组件与非受控组件在官网与国内网上的资料都不多，可能它像mixin是一种过度的设计。

但它们被设计出来，也反映了作者想让数据流全面掌管一切的初衷。

让数据掌管一切可以简单理解为，忠实地执行JSX的指令。

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

```jsx
<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。但非受控组件的出发点是忠实于用户操作，如果用户在代码中

```javascript
input.value = "xxxx"
```

以后
```jsx
<input defaultValue={this.state.value} />
```

就再不生效，一直是xxxx。

它怎么做到这一点，怎么辨识这个修改是来自框架内部或外部呢？我翻看了一下React的源码，原来它有一个叫valueTracker的东西跟踪用户的输入

```javascript
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`。


首先我为元素添加一个`_hijack`的属性，用来表示我已经劫持过defaultXXX。 然后描述对象 （`Object.defineProperty的第三个参数`）的set方法里面再添加一个开关，`_observing`。在框架内部更新视图，此值为false,更新完，它置为true。

这样就知晓 input.defaultValue = "xxx"时，这是由用户还是框架修改的。

```javascript
if (!dom._hijack) {//重写defaultXXX的setter,getter。
    dom._hijack = true;
    uncontrolledImpl.observe(dom, name);
}
dom._observing = false;//此时是框架在修改视图，因此需要关闭开关
dom[name] = val;
dom._observing = true; //打开 开关，来监听用户的修改行为
```

uncontrolledImpl的实现如下
```javascript
uncontrolledImpl.observe = function(dom, name) {
    try {
        var controllProp = name === "defaultValue" ? "value" : "checked";
        Object.defineProperty(dom, name, {
            set: function(value) {
                if (dom.type === "textarea") {
                    dom.innerHTML = value;
                }
                if (!dom._observing) {
                    if (!dom._setValue) {
                        var parsedValue = (dom[controllProp] = value);
                        dom._persistValue = Array.isArray(value) ? value : parsedValue;
                       // dom._setValue = true;
                    }
                } else {
                    //如果用户私下改变defaultValue，那么_setValue会被抺掉
                    dom._setValue = value == null ? false : true;
                }
                dom._defaultValue = value;
            },
            get: function() {
                return dom._defaultValue;
            },
            configurable: true
        });
    } catch (e) {}
};
```

又不小心贴了这么烧脑的代码，这是码农的坏毛病。不过，到这步，大家都明白，无论是官方react还是anu/qreact都是通过Object.defineProperty来根据用户的输入的。

于是我们可以理解以下的代码的行为了

｀``html
ReactDOM.render(<textarea defaultValue="foo" />, container);
ReactDOM.render(<textarea defaultValue="bar" />, container);
ReactDOM.render(<textarea defaultValue="noise" />, container);
expect(container.firstChild.defaultValue).toBe("noise");
```

由于用户一直没有手动修改 defaultValue，`dom._setValue` 一直为**false/undefined**，因此 `_persistValue` 一直能修改。

另一个例子:

```javascript
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");
```

此外受控组件与非受控组件也可以互相转换，这个过程不会影响 `_persistValue`。 

到这里我们可以总结以下

受控组件 通过事件实现，保证value/checked的值来源于JSX。

非受控组件，通过Object.defineProperty劫持defaulXXX的访问器，来控制


