ReactJS實用技巧(2):從新人大坑——表單組件來看State


不太清楚有多少初學React的同學和博主當時一樣,在看完React的生命周期、數據流之后覺得已經上手了,甩開文檔啪啪啪的開始敲了起來。結果...居然被一個input標簽給教做人了。

 

故事是這樣的:首先你創建了一個input標簽

var React = require('react'),
    ReactDOM = require('react-dom');

var Test = React.render(function() {
    render: function() {
        return (<input type="text" />);
    }
});

ReactDOM.render(<Text />, document.querySelector('#container'));

一切都是如此的輕松自然,接着由於需求你給input上設置了一個默認值:

<input value='123' type='text' />

突然你發現,唉我擦!輸入框里的值不能改動了,刪也刪不掉。你以為電腦卡死了,刷新了幾遍還是這樣。然而把value刪除就復原了,你不得不又返回去看文檔。

原理:在React中表單組件分為約束組件和無約束組件兩種。

  - 無約束組件,是指其value值不通過的props或者state來設置,僅由其自身來決定。表單組件的值的變化也不會被記錄,只能通過找到DOM節點的方式來獲取。

  - 約束組件,是React中推薦的表單的使用方式。表單組件的值並不是由其自身決定,而是通過父組件傳遞或者本身的state來控制。其內容的每次變化都會被保存,需要時僅需要通過this.state便能獲取。

約束狀態的input組件寫法如下:

var Test = Rreact.createClass({
  getInitialState: function() {
    return {value: ''}; 
  },
  render: function({
    return (<input type='text' value={this.state.value} onChange={this.handlerChange} />); 
  }),
  handlerChange: function(event) {
    var newValue = event.target.value;
    this.setState({value: newValue});
  }
});

上例中,我們監聽了input的onchange事件,每一次內容的更改實際上是更改組件的state屬性,通過state的變化來觸發DOM元素的變化。

React之所以這么做的原因,是因為React其實為一個狀態機,頁面上所有的DOM元素的狀態都需要被其所知所控制。

在繼續理解表單組件之前,組件的state是必須被開發者所理解的。通常很多人喜歡將state與props一起講解,這里博主認為通過state在表單組件的實際應用講解可能更加直觀。

 

State

每一本介紹React的書或文檔都會把state和props放在一起詳細的比較,其實最簡單的說:state是組件內部用來控制組件狀態的屬性,props是組件之間用來通信的屬性。

創建

state是通過名為getInitialState的生命周期函數創建的,其return出一個對象作為state值。如果你申明了該函數卻沒有返回值是會報錯的。

創建之后,在組件內部的所有函數都可以用 this.state.屬性名 來訪問該屬性。

修改

state的值並不是固定的,開發都通過在合適的時機改變它從而達到改變頁面展示的目的。

改變state的唯一是this.setState,該方法可以說是整個React系統的"扳機",正常情況(除了直接操作DOM)下所有的頁面更新都是由這個方法來觸發的。

var AddNum = React.createClass({
  getInitialState: function() {
      return {number: 0};
  },
  render:function() {
      return (<span onClick={this.handlerClick}>{this.state.number}</span>);
  },
  handlerClick: function() {
      var newNum = this.state.number;
      newNum++;
      this.setState({number: newNum});
  }
});

上例中,這個組件創建了一個span元素,值為0。當我們每次點擊該元素時數字便會加一。

解讀分析下代碼,首先我們通過getInitialState申明了該組件的state,包含一個number屬性,初值為0。

在handlerClick的事件里我有一個"多余"的步驟,明明可以簡化寫成:

this.setState({number: ++this.state.number});

而我先用一個中間變量newNum保存了state里的number屬性值,在newNum的基礎上更改。

這里就是有關state很重要的一點:絕對不要直接更改state的值,只通過setState來改變。否則會因為多個地方多次對state更改,導致不統一。從而引發一些不必要的問題。

當state里有多個屬性,如果需要更新某一個組件不用更新state里所有的屬性,只更新需要的就好:

{name: 'lilei', age: 25, sex: '男'} //state

this.setState({name: 'hanmeimei', sex: '女'}); //state:{name: 'hanmeimei', age: 25, sex: '女'}

更新時機

既然setState是React的扳機,那它就不能隨便在哪里都開槍。可能這部分東西需要對React的生命周期有一定掌握,許多文檔和博客里都寫得很詳細。我這里就不再抄書了。 

通常調用setState都是在人工觸發的事件里,比如上例中的handlerClick。但總有需要自動觸發的情形。生命周期主要分為創建、更新和銷毀三個階段。

  - 首先,在任何階段的render函數里都是不可以調用setState來觸發更新的。

  - 創建階段,一般是在componentWillMount以及componentDidMount這兩個生命周期函數中調用,前者表示React即將渲染真實DOM前的一個階段,也是最后的修改state的機會。后者表示真實DOM已經渲染完成,在頁面中能看到我們的組件了,這里再調用setState就會觸發組件的一次更新。在實際開發中通常用在下面這種情況:

var Foo = React.createClass({
  getInitialState:function() {
    return {....};
  },
  render: function() {
    return (<..../>);
  },
  componentDIdMount: function(){
    AJAX {
      this.setState({....});
    }   } });

大意就是首先創建出頁面元素,在componentDidMount函數中發起ajax之類的請求,獲取數據后通過setState更新頁面將數據更新到頁面中。

這樣做的好處就是在請求較慢或者請求失敗的情況下,頁面不至於留白,影響用戶體驗。

  - 更新階段,絕...對...不...要...在...這...個...階...段...調...用。因為如果在該階段任意一個生命周期函數中使用setState觸發頁面更新時,組件又會再次進入生命周期的更新階段,這里會再次調用setState方法,然后進入死循環。

  - 銷毀階段,就更不用說了,組件都沒得了,還更新個毛啊。

了解完了state,繼續看input的無約束組件。

<input defaultValue="123" />

如果設置了defaultValue屬性,該組件就是無約束組件。此時可以直接設置input的默認值,設置之后內容可以直接進行更改。缺點是這個屬性貌似只能設置一次,重復設置無效。

如果你不想為約束組件編寫如上那些繁瑣的過程,React提供了簡單的方法——mixin。

 

mixin

簡單來說mixin是用來抽象某一功能的工具,將邏輯抽象出來,使其可以在多個組件里復用。除了自定義以外,官方已經封裝了一系列的mixin組件,使用前需要引入react-with-addons文件。

var Form = React.createClass({
  mixins: [React.addons.LinkedStateMixin],
  getInitialState: function() {
      return {userName: '', passWord: ''};
  },
  render: function() {
      return (<div>
          <form>
            <input type='text' valueLink={this.linkState('userName')} placeholder='用戶名'  />
            <input type='password' valueLink={this.linkState('passWord')} placeholder='密碼' />
          </form>
        </div>);
  }
});

如上引入mixin組件后,只需要在input的特殊屬性valueLink中調用this.linkState('屬性名'),之后每次對input內容的更改就會同步到組件state中同名的屬性中。

其實LinkedStateMixin內部的實現跟我們Test那個示例組件里是一樣的,看懂了那段代碼就能理解這個mixin插件的內部原理了。

*雖然使用mixin可以簡化書寫流程,但是使用這種方式往數據流中添加定制功能時,復雜度會增加,建議只在特定場景下使用。傳統的約束表單組件更加靈活。

下面介紹下其他表單組件的內容

 

Label

label元素是表單中很重要的一個部分,由於for在JavaScript中是一個保留字,所以在JSX中for屬性更改為htmlFor。

<label htmlFor='name'>姓名</label>

 

Textarea

與傳統的HTML相比,在React中,textarea被修改為更像input的形式。

<textarea value={this.state.value} />

textarea的約束組件的使用方法與input一致,同時也可以使用同一個mixin。

<textarea valueLink={this.linkState('value')} />

使用defaultValue屬性同樣可以將textarea變為無約束組件。

<textarea defaultValue='請輸入內容' />

 

Select

在React中select與textarea一樣,相比HTML也作了一些修改,使它們操作起來更簡便。

無約束組件:

<select defaultValue='B'>
  <option value='A'>AAA</option>
  <option value='B'>BBB</option>
  <option value='C'>CCC</option>
</select>

約束組件:

var SelectComponent= React.createClass({
  getInitialState: function() {
    return {option: 'A'};
  },
  render: function() {
    return (<select value={this.state.option} onChange={this.handlerChange}>
          <option value='A'>A</option>
          <option value='B'>B</option>
          <option value='C'>C</option>
        </select>);
  },
  handlerChange: function(event) {
    this.setState({option: event.target.value});
  }

 

單選

約束組件:

var Radio = React.createClass({
  getInitialState: function() {
    return {gender: '男'};
  },
    render: function() {
    return (<div>
          <input type='radio' name='gender' value='男' checked={this.state.sex == '男'} onChange={this.handlerChange} />男
          <input type='radio' name='gender' value='女' checked={this.state.sex == '女'} onChange={this.handlerChange} />女
        </div>);   
  },
  handlerChange: function(event) {
    this.setState({gender: event.target.value});   } });

設置單選的defaultChecked會使其變為無約束組件。

<input type='radio' defaultChecked='true' />

 

復選

約束組件:

var CheckBox = React.createClass({
  getInitialState: function() {
    return {basketBall: false, swim: false, sing: false};
  },
  render: function() {
    return (<div>
          <p>愛好:</p>           <input type='checkbox' checked={this.state.basketBall} value='basketBall' onChange={this.handlerChange} />籃球           <input type='checkbox' checked={this.state.swim} value='swim' onChange={this.handlerChange} />游泳
          <input type='checkbox' checked={this.state.sing} value='sing' onChange={this.handlerChange} />唱歌         </div>);   },   handlerChange: function(event) {
    var type = event.target.value,
       checked = event.target.checked,
       newState = {};
    newState[type] = checked;
    
this.setState(newState);   } });

在handlerCheck函數中有一點要注意,我創建了一個中間變量newState。

handlerCheck: function(event) {
  var type = event.target.value,
     checked = event.target.value;
  this.setState(type: checked); //state: {basketBall: false, swim: false, sing: false, type: true} }

如果像上面的寫法,type並不會作為變量,而是作為字符串解析。每當你在setState時遇到困難時,嘗試中間變量,這方法百試不爽。

無約束組件:

<input type='checkbox' defaultChecked='true' />

 

多表單元素與change事件處理

在實際開發中通常有多個表單組件,為了使一個change處理器能處理所有的表單組件變化,可以使用bind方法來綁定類型。

var FormComponent = React.createClass({
  getInitialState: function() {
    return {name: '', gender: '男'};
  },
  render: function() {
    return (<form>
          <input type='text' value={this.state.name} onChange={this.handlerChange.bind(this,'name')} />
          <label htmlFor='male'>男</label>
          <input id='male'
              name='gender'
              type='radio'
              value='男'
              checked={this.state.gender == '男'}
              onChange={this.handlerChange.bind(this,'gender')} />
          <label htmlFor='female'>女</label>
          <input id='female'
              name='gender'
              type='radio'
              value='女'
              checked={this.state.gender == '女'}
              onChange={this.handlerChange.bind(this,'gender')} />
        </form>);
  },
  handlerChange: function(type, event) {
    var newState = {};
    newState[type] = event.target.value;
    this.setState(newState);
  }
});

 

表單是React初學者很容易踩的大坑,但是對表單組件的學習可以很快的理解state屬性。

感謝您的瀏覽,希望有所幫助。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM