>  기사  >  웹 프론트엔드  >  React로 제어되는 구성 요소와 제어되지 않는 구성 요소에 대한 자세한 설명

React로 제어되는 구성 요소와 제어되지 않는 구성 요소에 대한 자세한 설명

小云云
小云云원래의
2017-12-18 16:21:533001검색

이 글에서는 주로 React의 Controlled Component와 UnControlled Component에 대해 자세히 설명하고 있습니다. 공식 홈페이지와 국내 웹사이트에는 Controlled Component와 UnControlled Component에 대한 정보가 많지 않습니다. 상관없어. 이는 다양한 규모의 프로젝트 요구 사항을 충족하는 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

제어되는 구성 요소와 제어되지 않는 구성 요소는 React가 양식을 처리하는 진입점입니다. React의 관점에서 작성자는 데이터가 모든 것을 제어하도록 놔두거나 페이지의 생성 및 업데이트가 JSX 명령을 충실히 실행해야 한다는 점을 단순히 이해해야 합니다.

그러나 양식 요소에는 고유한 특수 기능이 있습니다. 사용자는 키보드 입력 및 마우스 선택을 통해 인터페이스 표시를 변경할 수 있습니다. 인터페이스의 변경은 일부 데이터가 변경되었음을 의미하기도 하며, 더 확실한 데이터는 입력의 , 텍스트 영역의 innerHTML 및 라디오/체크박스의 선택입니다. > , 수동적으로 수정되는 옵션의 selectedselectedIndex는 덜 명확합니다.

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");
input.value가 구성 요소의 state.value에서 가져온 경우 사용자가 입력을 수정한 후 JSX가 보기를 다시 새로 고친 후 input.value가 사용자의 새 값을 가져오나요 아니면 상태의 새 값을 가져오나요? ? 이러한 불일치를 바탕으로 React는 두 가지 모두가 지원하는 절충안을 제시했고, 오늘의 주제가 탄생했습니다. 🎜🎜React는 value/checked가 단독으로 존재할 수 없으며 onInput/onChange/disabed/readOnly 및 value/checked를 제어하는 ​​기타 속성이나 이벤트와 함께 사용해야 한다고 믿습니다. 이들은 함께 JSX에 의해 제어되는 제어 구성요소를 형성합니다. 사용자가 이러한 추가 속성 및 이벤트를 작성하지 않으면 프레임워크는 내부적으로 onClick, onInput 및 onChange와 같은 일부 이벤트를 추가하여 사용자가 해당 값을 입력하거나 선택하지 못하게 하고 해당 값을 수정하지 못하게 합니다. 프레임워크 내부에는 JSX에 의해 마지막으로 할당된 값을 유지하고 내부 이벤트에 의해서만 수정될 수 있는 persistValue라는 완고한 변수가 있습니다. 🎜🎜그러므로 제어되는 구성 요소는 이벤트를 통해 달성할 수 있는 가치의 제어라고 주장할 수 있습니다. 🎜🎜제어되는 구성 요소에서 persistValue는 항상 새로 고쳐질 수 있습니다. 🎜🎜제어되지 않은 구성요소를 다시 살펴보겠습니다. value/checked가 이미 사용 중이므로 React는 HTML에서 무시되는 또 다른 속성 세트인 defaultValue/defaultChecked를 활성화합니다. 일반적으로 value/checked와 유사하다고 생각합니다. 즉, value가 존재하지 않는 경우 defaultValue의 값을 value로 간주합니다. 🎜🎜위에서 양식 요소의 표시는 내부 persistValue에 의해 제어되므로 defaultXXX도 persistValue를 동기화한 다음 persistValue가 DOM을 동기화한다고 말했습니다. 그러나 제어되지 않는 구성 요소의 시작점은 사용자 작업에 충실하는 것입니다. 코드에서 사용자가 🎜
//精确匹配
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);//选中第三个
🎜인 경우 더 이상 적용되지 않으며 항상 xxxx입니다. 🎜🎜이 작업을 수행하는 방법과 이 수정 사항이 프레임워크 내부에서 발생하는지 외부에서 발생하는지 식별하는 방법은 무엇입니까? React의 소스코드를 살펴보니 사용자 입력을 추적하는 valueTracker라는 것이 있더라구요🎜
//模糊匹配
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.defineProperty를 통해 요소의 값에 입력/확인이 되기 때문에 사용자의 접근을 알 수 있습니다. 값 할당 작업입니다. 🎜🎜그러나 값/확인됨은 여전히 ​​너무 많은 내부 메커니즘(예: 값 및 oninput, onchange, 입력 방법 이벤트 oncompositionstart,
compositionchange, oncompositionend, onpaste, oncut)을 포함하는 두 가지 핵심 속성입니다. 값을 수정/확인한 경우
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如何实现菜单权限控制?

React 内部机制探秘

React中组件的写法有哪些


위 내용은 React로 제어되는 구성 요소와 제어되지 않는 구성 요소에 대한 자세한 설명의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!

성명:
본 글의 내용은 네티즌들의 자발적인 기여로 작성되었으며, 저작권은 원저작자에게 있습니다. 본 사이트는 이에 상응하는 법적 책임을 지지 않습니다. 표절이나 침해가 의심되는 콘텐츠를 발견한 경우 admin@php.cn으로 문의하세요.