React受控元件和非受控元件

看風景就發表於2018-09-06

受控元件和非受控元件主要是用來解決表單元件狀態誰來控制的問題。因為使用者的輸入會反應在介面上,相當於檢視的狀態發生了變化,而react是通過虛擬DOM比對修改檢視的,這裡就要決定誰來控制表單元件的狀態。由使用者直接控制的稱為非受控元件,而由react的控制的,稱為受控元件。

1. 非受控元件

沒有設定value/checked的表單元件,表單資料有DOM元素本身處理
元件自己控制元件的狀態,一般父元件會給它一個初始值(通過defaultValue屬性)
defaultValue/defaultChecked設定預設值,首次生效,重新渲染後則不生效,有value時也不生效
input元素本身也有defaultValue屬性,可以設定或返回其預設值
defaultValue需要在constructor或者componentWillMount設定設定state
其他週期中設定的在render後無法看到預設值

形如:

 <input type="text" />

非受控元件與普通input一致,使用者的輸入正常顯示,由使用者來控制元件的狀態。

非受控元件通常和ref結合在一起,通過node.value方式來獲取dom節點的值,這種方式適合不需要知道實時狀態,只需要提交的表單元件。

import React, { Component } from 'react';

class UnControlled extends Component {
    handleSubmit = (e) => {
        console.log(e);
        e.preventDefault();
        console.log(this.name.value);
    }
    render() {
        return (
            <form onSubmit={this.handleSubmit}>
                <input type="text" ref={i => this.name = i} defaultValue="BeiJing" />
                <button type="submit">Submit</button>
            </form>
        );
    }
}

export default UnControlled;

非受控元件的父子元件形式:

//父元件
class Parent extends Component {
    render() {
        return  
            <NumberInput defaultValue=20 />;
    }
}

//子元件NumberInput
class NumberInput extends Component {
    render() {
        return   
            <input type="text" className="NumberInput"
            defaultValue={this.props.defaultValue} />
        
    }
}

2. 受控元件

設定了value/checked的表單元件,其vaule/checked必須是react元件的state,並且有onChange事件來修改state
元件自己不更新值,而是把值傳遞給父元件,讓父元件決定是否更改。

形如:

<input value="this.state.name" onChange="fHandleChange" />

受控元件通常如下:

<input
    type="text"
    value={this.state.value}
    onChange={(e) => {
        this.setState({
            value: e.target.value.toUpperCase(),
        });
    }}
/>

1. 可以通過初始state中設定表單的預設值;
2. 每當表單的值發生變化時,呼叫onChange事件處理器;
3. 事件處理器通過合成事件物件e拿到改變後的狀態,並更新應用的state.
4. setState觸發檢視的重新渲染,完成表單元件值得更新

受控元件不同表單元件的處理形式:

受控元件的說明:

1. 受控元件會頻繁觸發change事件,會帶來一定的效能損耗,但是影響不大,為了狀態的統一,值得一做
2. 受控元件要value和onChange事件配合使用,寫起來麻煩,有一些簡化手段:
  1. 封裝一個InputItem元件,將value和change作為引數
  2. 使用一個onChange方法處理多個Input,內部使用switch來分別處理不同元件的狀態

受控元件的父子元件形式:

//父元件
class Parent extends Component {
    constructor(props) {
        super(props);
        this.state = {value: 1};
        this.onChangeHandle = this.onChange.bind(this);
    }
    onChange(event) {
        //處理值
        if (改) {
        this.setState({value: event.target.value});
        }
    }
    render() {
        return  
            <NumberInput value={this.state.value} onChange={this.onChangeHandle} />
        ;
    }
}
//子元件NumberInput
class NumberInput extends Component {
    constructor(props) {
        super(props);
        this.state = {
        value: props.value
        };
        
        this.onChangeHandle = this.onChange.bind(this);
    }
    //接收到新的屬性時更新state
    componentWillReceiveProps(nextProps) {
        this.setState({
        value: nextProps.value
        });
    }
    onChange(event) {
        //通知父元件值更新了
        this.props.onChange(event);
    }
    render() {
        return 
            <input type="text" className="NumberInput"
            value={this.state.value}
            onChange={this.onChangeHandle} />
    }
}

3. 混合元件(既支援受控元件也支援非受控元件的元件)

//父元件
//把子元件當受控元件(傳遞value和onChange):
render() {
    return <NumberInput value={this.state.value} onChange={this.onChangeHandle} />
}
//把子元件當非受控元件(傳遞defalutValue):
render() {
    return <NumberInput defaultValue={this.state.value} />
}

//子元件NumberInput
class NumberInput extends Component {
    constructor(props) {
        super(props);
        this.state = {
            value: props.value
        };
        this.onChangeHandle = this.onChange.bind(this);
    }
    //接收到新的屬性時更新state
    componentWillReceiveProps(nextProps) {
        this.setState({
            value: nextProps.value
        });
    }
    onChange(event) {
        //通知父元件值更新了
        this.props.onChange(event);
    }
    render() {
        let val = {};
        //判斷元件的props屬性是否有value屬性,有的話,是受控元件,沒有的話是非受控元件
        this.props['value'] === undefined ?
            (val = {'defaultValue': this.props.defaultValue}) :
            (val = {'value': this.state.value});
        //使用物件和分解運算子來完成不同屬性的切換
        return  <input type="text" className="NumberInput"
                {...val}
                onChange={this.onChangeHandle} />;
    }
}

混合元件的基本原則:

1. props.value總是有比內部state.value跟高的優先順序
2. 元件中所有的變化都應該同步到內部的state.value,並通過執行props.onChange來觸發更新
3. 當元件接受新的props的時候,將props.value反映給state.value
4. 在優先值發生變化後更新元件

有了以上原則,就可以實現一個裝飾器,hybridCtrl,來將一個普通元件轉換為一個混合元件

@hybridCtrl
class App extends React.Component {
    static propTypes = {
        value: React.PropTypes.any,
    }

    state = {
        _value: '',
    }

    mapPropToState(controlledValue) {
        // your can do some transformations from `props.value` to `state._value`
    }

    handleChange(newVal) {
        // it's your duty to handle change events and dispatch `props.onChange`
    }
}

結論

1、為什麼我們需要混合元件?

我們應該建立同時受控和非受控的元件,就像原生的元素那樣。

2、混合元件主要思想是?

同時維護props.value和state.value的值。props.value在展示上擁有更高的優先順序,state.value代表著元件真正的值。

混合元件的一個例子(作者是一村又一莊):

1、支援傳入預設值;
2、可控:元件外部修改props可改變input元件的真實值及顯示值;
3、非可控:輸入框中輸入值,可同時改變input元件的真實值及顯示值。

示例程式碼地址:https://github.com/abell123456/hybrid-component

4. 受控元件和不受控元件的使用區別

5. 表單元件的幾個重要屬性

1.狀態屬性

React的form元件提供了幾個重要的屬性,用來顯示元件的狀態

value: 型別為text的input元件,textarea元件以及select元件都藉助value prop來展示應用的狀態
checked: 型別為radio或checkbox的元件藉助值為boolean型別的selected prop來展示應用的狀態
selected: 該屬性可作用於select元件下面的option上,React並不建議這種方式表示狀態.而推薦在select元件上使用value的方式

2.事件屬性

當狀態屬性改變時會觸發onChange事件屬性.受控元件中的change事件與HTML DOM中提供的input事件更為類似

 

 

參考:https://segmentfault.com/a/1190000012404114
   https://blog.csdn.net/jianruoche/article/details/79238831
         https://blog.csdn.net/fendouzhe123/article/details/52121704
         https://www.cnblogs.com/aichenxy/p/6758004.html

相關文章