React入門教程(6)React表單處理及狀態(tài)提升

表單

HTML表單元素與React中的其他DOM元素有所不同,因為表單元素生來就保留一些內(nèi)部狀態(tài)。例如嗤疯,下面這個表單只接受一個唯一的name革骨。

<form>
  <label>
    Name:
    <input type="text" name="name" />
  </label>
  <input type="submit" value="Submit" />
</form>

當(dāng)用戶提交表單時是复,HTML的默認行為會使這個表單跳轉(zhuǎn)到一個新頁面募强。在React中亦是如此幔睬。但大多數(shù)情況下背亥,我們都會構(gòu)造一個處理提交表單并可訪問用戶輸入表單數(shù)據(jù)的函數(shù)般眉。實現(xiàn)這一點的標(biāo)準(zhǔn)方法是使用一種稱為“受控組件”的技術(shù)我纪。

受控組件

在HTML當(dāng)中慎宾,像<input>,<textarea>, 和 <select>這類表單元素會維持自身狀態(tài),并根據(jù)用戶輸入進行更新浅悉。但在React中趟据,可變的狀態(tài)通常保存在組件的狀態(tài)屬性中,并且只能用 setState()方法進行更新术健。

我們通過使react變成一種單一數(shù)據(jù)源的狀態(tài)來結(jié)合二者汹碱。React負責(zé)渲染表單的組件仍然控制用戶后續(xù)輸入時所發(fā)生的變化。相應(yīng)的荞估,其值由React控制的輸入表單元素稱為“受控組件”咳促。

例如,我們想要使上個例子中在提交表單時輸出name,我們可以寫成“受控組件”的形式:

class NameForm extends React.Component {
  constructor(props) {
    super(props);
    this.state = {value: ''};

    this.handleChange = this.handleChange.bind(this);
    this.handleSubmit = this.handleSubmit.bind(this);
  }

  handleChange(event) {
    this.setState({value: event.target.value});
  }

  handleSubmit(event) {
    alert('A name was submitted: ' + this.state.value);
    event.preventDefault();
  }

  render() {
    return (
      <form onSubmit={this.handleSubmit}>
        <label>
          Name:
          <input type="text" value={this.state.value} onChange={this.handleChange} />
        </label>
        <input type="submit" value="Submit" />
      </form>
    );
  }
}

在 CodePen 上嘗試勘伺。

由于 value 屬性是在我們的表單元素上設(shè)置的跪腹,因此顯示的值將始終為 React數(shù)據(jù)源上this.state.value 的值。由于每次按鍵都會觸發(fā) handleChange 來更新當(dāng)前React的state飞醉,所展示的值也會隨著不同用戶的輸入而更新冲茸。

使用"受控組件",每個狀態(tài)的改變都有一個與之相關(guān)的處理函數(shù)。這樣就可以直接修改或驗證用戶輸入冒掌。例如噪裕,我們?nèi)绻胂拗戚斎肴渴谴髮懽帜福覀兛梢詫?code>handleChange 寫為如下:

handleChange(event) {
  this.setState({value: event.target.value.toUpperCase()});
}

textarea 標(biāo)簽

在HTML當(dāng)中股毫,<textarea> 元素通過子節(jié)點來定義它的文本內(nèi)容

<textarea>
  Hello there, this is some text in a text area
</textarea>

在React中膳音,<textarea>會用value屬性來代替。這樣的話铃诬,表單中的<textarea> 非常類似于使用單行輸入的表單:

class EssayForm extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      value: 'Please write an essay about your favorite DOM element.'
    };

    this.handleChange = this.handleChange.bind(this);
    this.handleSubmit = this.handleSubmit.bind(this);
  }

  handleChange(event) {
    this.setState({value: event.target.value});
  }

  handleSubmit(event) {
    alert('An essay was submitted: ' + this.state.value);
    event.preventDefault();
  }

  render() {
    return (
      <form onSubmit={this.handleSubmit}>
        <label>
          Name:
          <textarea value={this.state.value} onChange={this.handleChange} />
        </label>
        <input type="submit" value="Submit" />
      </form>
    );
  }
}

注意this.state.value是在構(gòu)造函數(shù)中初始化祭陷,這樣文本區(qū)域就能獲取到其中的文本。

select 標(biāo)簽

在HTML當(dāng)中趣席,<select>會創(chuàng)建一個下拉列表兵志。例如這個HTML就創(chuàng)建了一個下拉列表的原型。

<select>
  <option value="grapefruit">Grapefruit</option>
  <option value="lime">Lime</option>
  <option selected value="coconut">Coconut</option>
  <option value="mango">Mango</option>
</select>

請注意宣肚,Coconut選項最初由于selected屬性是被選中的想罕。在React中,并不使用之前的selected屬性,而在根select標(biāo)簽上用value屬性來表示選中項按价。這在受控組件中更為方便惭适,因為你只需要在一個地方來更新組件。例如:

class FlavorForm extends React.Component {
  constructor(props) {
    super(props);
    this.state = {value: 'coconut'};

    this.handleChange = this.handleChange.bind(this);
    this.handleSubmit = this.handleSubmit.bind(this);
  }

  handleChange(event) {
    this.setState({value: event.target.value});
  }

  handleSubmit(event) {
    alert('Your favorite flavor is: ' + this.state.value);
    event.preventDefault();
  }

  render() {
    return (
      <form onSubmit={this.handleSubmit}>
        <label>
          Pick your favorite La Croix flavor:
          <select value={this.state.value} onChange={this.handleChange}>
            <option value="grapefruit">Grapefruit</option>
            <option value="lime">Lime</option>
            <option value="coconut">Coconut</option>
            <option value="mango">Mango</option>
          </select>
        </label>
        <input type="submit" value="Submit" />
      </form>
    );
  }
}

在 CodePen 上嘗試楼镐。

總之癞志,<input type="text">, <textarea>, 和 <select> 都十分類似 - 他們都通過傳入一個value屬性來實現(xiàn)對組件的控制。

file input 標(biāo)簽

在HTML當(dāng)中框产,<input type="file"> 允許用戶從他們的存儲設(shè)備中選擇一個或多個文件以提交表單的方式上傳到服務(wù)器上, 或者通過 Javascript 的 File API 對文件進行操作 凄杯。

<input type="file" />

由于該標(biāo)簽的 value 屬性是只讀的, 所以它是 React 中的一個非受控組件秉宿。我們會把它和其他非受控組件一起在后面的章節(jié)進行詳細的介紹戒突。

多個輸入的解決方法

當(dāng)你有處理多個受控的input元素時,你可以通過給每個元素添加一個name屬性蘸鲸,來讓處理函數(shù)根據(jù) event.target.name的值來選擇做什么妖谴。

例如:

class Reservation extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      isGoing: true,
      numberOfGuests: 2
    };

    this.handleInputChange = this.handleInputChange.bind(this);
  }

  handleInputChange(event) {
    const target = event.target;
    const value = target.type === 'checkbox' ? target.checked : target.value;
    const name = target.name;

    this.setState({
      [name]: value
    });
  }

  render() {
    return (
      <form>
        <label>
          Is going:
          <input
            name="isGoing"
            type="checkbox"
            checked={this.state.isGoing}
            onChange={this.handleInputChange} />
        </label>
        <br />
        <label>
          Number of guests:
          <input
            name="numberOfGuests"
            type="number"
            value={this.state.numberOfGuests}
            onChange={this.handleInputChange} />
        </label>
      </form>
    );
  }
}

在 CodePen 上嘗試窿锉。

注意我們?nèi)绾问褂肊S6當(dāng)中的計算屬性名語法來更新與給定輸入名稱相對應(yīng)的狀態(tài)鍵:

this.setState({
  [name]: value
});

相當(dāng)于如下ES5語法

var partialState = {};
partialState[name] = value;
this.setState(partialState);

同樣由于 setState() 自動將部分狀態(tài)合并到當(dāng)前狀態(tài)酌摇,因此我們只需要使用發(fā)生變化的部分調(diào)用它。

受控組件的替代方法

有時使用受控組件可能很繁瑣嗡载,因為您要為數(shù)據(jù)可能發(fā)生變化的每一種方式都編寫一個事件處理程序窑多,并通過一個組件來管理全部的狀態(tài)。當(dāng)您將預(yù)先存在的代碼庫轉(zhuǎn)換為React或?qū)eact應(yīng)用程序與非React庫集成時洼滚,這可能變得特別煩人埂息。在以上情況下,你或許應(yīng)該看看非受控組件遥巴,這是一種表單的替代技術(shù)千康。

綜合自定義表單校驗案例

import React, { Component } from 'react';

class FormSub extends Component {
  constructor(opt) {
    super(opt);
    this.state = {
      Title: 'hi',
      Validate: {
        Title: {
          required: true,
          minLen: 6,
          maxLen: 10,
          validate: true,
          msg: '*ToDo不能為空!'
        }
      }
    }
  }

  handlerChange = (e) => {
    // 設(shè)置狀態(tài):是異步執(zhí)行铲掐。
    this.setState({
      [e.target.name]: e.target.value
    }, () => {
      this.validateInput();
    });
  }

  handlerSubmit = (e) => {
    e.preventDefault();
    // 第一: 做表單的校驗
    this.validateInput();
    // 第二: 做表單提交到后臺ajax請求
  };

  validateInput() {
    let { Title, Validate } = this.state;
    let tempValidate = false;
    const len = Title.length;
    const min = Validate.Title.minLen;
    const max = Validate.Title.maxLen;
    if(len >= min && len <= max) {
      tempValidate = true;
    }

    this.setState(preState => {
      return Object.assign({}, preState, {
        Validate: {
          Title: Object.assign({}, preState.Validate.Title,{
            validate: tempValidate,
          })
        }
      });
    })
  }

  render() {
    return (
      <form onSubmit={this.handlerSubmit}>
        <label>
          ToDo:
          <input 
            type="text"
            name="Title"
            onChange={this.handlerChange}
            value={this.state.Title}
          />
          {
            !this.state.Validate.Title.validate &&
            <span 
              style={{color: 'red'}}
            >
              {this.state.Validate.Title.msg}
            </span>
          }
        </label>
        <br/>
        <input type="submit" value="提交"/>
      </form>
    );
  }
}

export default FormSub;

狀態(tài)提升

使用 react 經(jīng)常會遇到幾個組件需要共用狀態(tài)數(shù)據(jù)的情況拾弃。這種情況下,我們最好將這部分共享的狀態(tài)提升至他們最近的父組件當(dāng)中進行管理摆霉。我們來看一下具體如何操作吧

我們一個計數(shù)的父組件豪椿,兩個按鈕組件,兩個按鈕組件分別對父組件中的數(shù)據(jù)進行添加和減少操作携栋。

// Counter.js 父組件
import React, { Component } from 'react';

import ButtonAdd from './ButtonAdd';
import ButtonMinus from './ButtonMinus';

class Counter extends Component {
  constructor(option) {
    super(option);
    this.state = { num: 0, age: 19 };
  }
  minusCount(num, e) {
    this.setState((preState) => {
      return { num: preState.num - num }
    });
  }
  addCount(num, e) {
    this.setState((preState) => {
      return { num: preState.num + num }
    });
  }
  render() {
    return (
      <div>
        <p>parent: { this.state.num } -{ this.state.age }</p>
        <hr />
        <ButtonAdd addCount={ this.addCount.bind(this) } num={ this.state.num } />
        <ButtonMinus minusCount={ this.minusCount.bind(this) } num={ this.state.num }  />
      </div>
    );
  }
}

export default Counter;

// 子組件 添加按鈕組件
import React, { Component } from 'react';

class ButtonAdd extends Component {
  render() {
    return (
      <div>
        <span>child:state {this.props.num}</span>
        <button onClick={ () => {
          this.props.addCount(1);
        }}>
          +1
        </button>
      </div>
    );
  }
}

export default ButtonAdd;

// 子組件:  減少按鈕組件
import React, { Component } from 'react';

class ButtonMinus extends Component {
  render() {
    return (
      <div>
        <span>child:state { this.props.num }</span>
        <button onClick={ () => {
          this.props.minusCount(1);
        }}>
          -1
        </button>
      </div>
    );
  }
}

export default ButtonMinus;

組合與props.children

React 具有強大的組合模型搭盾,我們建議使用組合而不是繼承來復(fù)用組件之間的代碼。

在本節(jié)中婉支,我們將圍繞幾個 React 新手經(jīng)常使用繼承解決的問題鸯隅,我們將展示如何用組合來解決它們。

包含關(guān)系

一些組件不能提前知道它們的子組件是什么向挖。這對于 SidebarDialog 這類通用容器尤其常見蝌以。

我們建議這些組件使用 children 屬性將子元素直接傳遞到輸出霎奢。

function FancyBorder(props) {
  return (
    <div className={'FancyBorder FancyBorder-' + props.color}>
      {props.children}
    </div>
  );
}

這樣做還允許其他組件通過嵌套 JSX 來傳遞子組件。

function WelcomeDialog() {
  return (
    <FancyBorder color="blue">
      <h1 className="Dialog-title">
        Welcome
      </h1>
      <p className="Dialog-message">
        Thank you for visiting our spacecraft!
      </p>
    </FancyBorder>
  );
}

在 CodePen 上試試饼灿。

<FancyBorder> JSX 標(biāo)簽內(nèi)的任何內(nèi)容都將通過 children 屬性傳入 FancyBorder幕侠。由于 FancyBorder 在一個 <div> 內(nèi)渲染了 {props.children},所以被傳遞的所有元素都會出現(xiàn)在最終輸出中碍彭。

雖然不太常見晤硕,但有時你可能需要在組件中有多個入口,這種情況下你可以使用自己約定的屬性而不是 children

function SplitPane(props) {
  return (
    <div className="SplitPane">
      <div className="SplitPane-left">
        {props.left}
      </div>
      <div className="SplitPane-right">
        {props.right}
      </div>
    </div>
  );
}

function App() {
  return (
    <SplitPane
      left={
        <Contacts />
      }
      right={
        <Chat />
      } />
  );
}

在 CodePen 上試試庇忌。

類似 <Contacts /><Chat /> 這樣的 React 元素都是對象舞箍,所以你可以像任何其他元素一樣傳遞它們。

特殊實例

有時我們認為組件是其他組件的特殊實例皆疹。例如疏橄,我們會說 WelcomeDialogDialog 的特殊實例。

在 React 中略就,這也是通過組合來實現(xiàn)的捎迫,通過配置屬性用較特殊的組件來渲染較通用的組件。

function Dialog(props) {
  return (
    <FancyBorder color="blue">
      <h1 className="Dialog-title">
        {props.title}
      </h1>
      <p className="Dialog-message">
        {props.message}
      </p>
    </FancyBorder>
  );
}

function WelcomeDialog() {
  return (
    <Dialog
      title="Welcome"
      message="Thank you for visiting our spacecraft!" />
  );
}

在 CodePen 上試試表牢。

組合對于定義為類的組件同樣適用:

function Dialog(props) {
  return (
    <FancyBorder color="blue">
      <h1 className="Dialog-title">
        {props.title}
      </h1>
      <p className="Dialog-message">
        {props.message}
      </p>
      {props.children}
    </FancyBorder>
  );
}

class SignUpDialog extends React.Component {
  constructor(props) {
    super(props);
    this.handleChange = this.handleChange.bind(this);
    this.handleSignUp = this.handleSignUp.bind(this);
    this.state = {login: ''};
  }

  render() {
    return (
      <Dialog title="Mars Exploration Program"
              message="How should we refer to you?">
        <input value={this.state.login}
               onChange={this.handleChange} />
        <button onClick={this.handleSignUp}>
          Sign Me Up!
        </button>
      </Dialog>
    );
  }

  handleChange(e) {
    this.setState({login: e.target.value});
  }

  handleSignUp() {
    alert(`Welcome aboard, ${this.state.login}!`);
  }
}

在 CodePen 上試試窄绒。

參考

  1. 官網(wǎng)文檔
  2. 老馬React視頻地址: https://ke.qq.com/course/379234?tuin=1eb4a0a4
  3. AICODER官網(wǎng)地址:https://www.aicoder.com/
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市崔兴,隨后出現(xiàn)的幾起案子彰导,更是在濱河造成了極大的恐慌,老刑警劉巖敲茄,帶你破解...
    沈念sama閱讀 219,490評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件位谋,死亡現(xiàn)場離奇詭異,居然都是意外死亡堰燎,警方通過查閱死者的電腦和手機掏父,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,581評論 3 395
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來爽待,“玉大人损同,你說我怎么就攤上這事∧窨睿” “怎么了膏燃?”我有些...
    開封第一講書人閱讀 165,830評論 0 356
  • 文/不壞的土叔 我叫張陵,是天一觀的道長何什。 經(jīng)常有香客問我组哩,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,957評論 1 295
  • 正文 為了忘掉前任伶贰,我火速辦了婚禮蛛砰,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘黍衙。我一直安慰自己泥畅,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 67,974評論 6 393
  • 文/花漫 我一把揭開白布琅翻。 她就那樣靜靜地躺著位仁,像睡著了一般。 火紅的嫁衣襯著肌膚如雪方椎。 梳的紋絲不亂的頭發(fā)上聂抢,一...
    開封第一講書人閱讀 51,754評論 1 307
  • 那天,我揣著相機與錄音棠众,去河邊找鬼琳疏。 笑死,一個胖子當(dāng)著我的面吹牛闸拿,可吹牛的內(nèi)容都是我干的空盼。 我是一名探鬼主播,決...
    沈念sama閱讀 40,464評論 3 420
  • 文/蒼蘭香墨 我猛地睜開眼胸墙,長吁一口氣:“原來是場噩夢啊……” “哼我注!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起迟隅,我...
    開封第一講書人閱讀 39,357評論 0 276
  • 序言:老撾萬榮一對情侶失蹤励七,失蹤者是張志新(化名)和其女友劉穎智袭,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體掠抬,經(jīng)...
    沈念sama閱讀 45,847評論 1 317
  • 正文 獨居荒郊野嶺守林人離奇死亡吼野,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,995評論 3 338
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了两波。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片瞳步。...
    茶點故事閱讀 40,137評論 1 351
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖腰奋,靈堂內(nèi)的尸體忽然破棺而出单起,到底是詐尸還是另有隱情,我是刑警寧澤劣坊,帶...
    沈念sama閱讀 35,819評論 5 346
  • 正文 年R本政府宣布嘀倒,位于F島的核電站,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏测蘑。R本人自食惡果不足惜灌危,卻給世界環(huán)境...
    茶點故事閱讀 41,482評論 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望碳胳。 院中可真熱鬧勇蝙,春花似錦、人聲如沸挨约。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,023評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽烫罩。三九已至惜傲,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間贝攒,已是汗流浹背盗誊。 一陣腳步聲響...
    開封第一講書人閱讀 33,149評論 1 272
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留隘弊,地道東北人哈踱。 一個月前我還...
    沈念sama閱讀 48,409評論 3 373
  • 正文 我出身青樓,卻偏偏與公主長得像梨熙,于是被迫代替她去往敵國和親开镣。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 45,086評論 2 355

推薦閱讀更多精彩內(nèi)容