首頁  >  文章  >  web前端  >  在React中詳細介紹受控組件與非受控組件

在React中詳細介紹受控組件與非受控組件

亚连
亚连原創
2018-06-15 14:47:471428瀏覽

這篇文章主要介紹了淺談React深度編程之受控組件與非受控組件,現在分享給大家,也給大家做個參考。

受控組件與非受控組件在官網與國內網上的資料都不多,有些人覺得它可有可不有,也不在意。這正好顯示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 = &#39;&#39; + 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) ? 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");

純文字類別: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之流则可能遇坑。

上面是我整理给大家的,希望今后会对大家有帮助。

相关文章:

在vue.js中如何实现数据分发slot

在Vue中有关使用ajax方法有哪些?

通过vue如何引入公共css文件

以上是在React中詳細介紹受控組件與非受控組件的詳細內容。更多資訊請關注PHP中文網其他相關文章!

陳述:
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn