React系統(tǒng)性學(xué)習(xí)(下)

$ 前言

? 在《React系統(tǒng)性學(xué)習(xí)(上)》中我們主要學(xué)習(xí)了

  1. 什么是React
  2. JSX語(yǔ)法
  3. 元素渲染
  4. 組件(Component) 和 屬性 (Props)
  5. 狀態(tài)(State) 和 生命周期(lifeCircle)
  6. 處理事件
  7. 條件渲染

本文我們將繼續(xù)學(xué)習(xí)

  1. 列表(List) 和 鍵(keys)
  2. 表單(Forms)
  3. 狀態(tài)提升(Lifting State Up)
  4. 組合 VS 繼承 (Composition vs inheritance)

$ 版本聲明

? 本文使用版本 React v16.2.0

$ 列表 和 鍵

? 列表(List)校镐, 鍵(Key)
? 回顧一下在javascript中如何轉(zhuǎn)換列表:在數(shù)組中使用map()函數(shù)對(duì)numbers數(shù)組中的每個(gè)元素依次執(zhí)操作

const numbers = [1, 2, 3, 4, 5];
const doubled = numbers.map((number) => number * 2);
console.log(doubled)  // 2, 4, 6, 8, 10

? React 基本借鑒了以上寫法式撼,只不過(guò)將數(shù)組替換成了元素列表

多組件渲染

? 可以創(chuàng)建元素集合,并用一對(duì)大括號(hào) {} 在 JSX 中直接將其引用即可

? 下面蔚万,我們用 JavaScript 的 map() 函數(shù)將 numbers 數(shù)組循環(huán)處理州邢。對(duì)于每一項(xiàng)动壤,我們返回一個(gè) <li> 元素兢卵。最終蜈首,我們將結(jié)果元素?cái)?shù)組分配給 listItems

const numbers = [1, 2, 3, 4, 5];
const listItems = numbers.map((number) => 
    <li>{number}</li>
)

? 再把整個(gè) listItems 數(shù)組包含到一個(gè) <ul> 元素,并渲染到DOM

ReactDOM.render(
    <ul>{listItems}</ul>,
    document.getElementById('root')
)
基本列表組件

? 通常情況下肃晚,我們會(huì)在一個(gè)組件中渲染列表而不是直接放到root上锚贱。重構(gòu)一下上例

function NumberList(props) {
    const numbers = props.number;
    const listItems = numbers.map((number) => 
        <li>{number}</li>
    )
    return (
        <ul>{listItems}</ul>
    )
}

const numbers = [1, 2, 3, 4, 5];

ReactDOM.render(
    <NumberList numbers={number} />,
    document.getElementById('root')
)

? 當(dāng)運(yùn)行上述代碼的時(shí)候,將會(huì)受到一個(gè)警告:a key should be provided for list items关串,要求應(yīng)該為元素提供一個(gè)鍵(注:min版本react無(wú)提示)拧廊。要去掉這個(gè)警告也簡(jiǎn)單,只需要在listItem的每個(gè)li中增加key屬性即可悍缠,增加后的每個(gè)<li>如下

function NumberList(props) {
  const numbers = props.numbers;
  const listItems = numbers.map((number) =>
    <li key={number.toString()}>
      {number}
    </li>
  );
  return (
    <ul>{listItems}</ul>
  );
}

? 當(dāng)創(chuàng)建元素列表時(shí)卦绣,“key” 是一個(gè)你需要包含的特殊字符串屬性耐量,那為什么要包含呢飞蚓?

鍵(Keys)

? 鍵Keys 幫助React標(biāo)識(shí)那個(gè)項(xiàng)被修改、添加或者移除了廊蜒。數(shù)組中的每一個(gè)元素都應(yīng)該有一個(gè)唯一不變的鍵來(lái)標(biāo)識(shí)趴拧。

?挑選key最好的辦法是使用一個(gè)在它的同輩元素中不重復(fù)的表示字符串。多數(shù)情況下可以使用數(shù)據(jù)中的IDs來(lái)作為Keys山叮。但是還是會(huì)遇到?jīng)]有id字段的數(shù)據(jù)著榴,這種情況你可以使用數(shù)據(jù)項(xiàng)的索引值

cosnt todoItems = todos.map((todo, index) => 
    // 數(shù)據(jù)項(xiàng)沒(méi)有IDs時(shí)使用該辦法
    <li key={index}>
        {todo.text}
    </li>
)

? 如果列表項(xiàng)可能被重新排序,這種用法存在一定的性能問(wèn)題屁倔,React會(huì)產(chǎn)生時(shí)間復(fù)雜度為O(n^3)的算法執(zhí)行脑又。因此優(yōu)先使用數(shù)據(jù)項(xiàng)本身的字段內(nèi)容來(lái)設(shè)置鍵

使用 Keys 提取組件

? Keys只有在數(shù)組的上下文中存在意義。例如锐借,如果你提取了一個(gè)ListItem組件问麸,應(yīng)該把key放置在數(shù)組處理的<ListItem />元素中,而不能放在ListItem組件自身的<li>根元素上钞翔。

?以下的用法就是錯(cuò)誤的

function ListItem(props) {
    const value = props.value;
    return (
        // 錯(cuò)誤严卖!不需要再這里指定 key
        <li key={value.toString()}> {value}</li>
    )
}

function NumberList(props) {
  const numbers = props.numbers;
  const listItems = numbers.map((number) =>
    // 錯(cuò)誤!key 應(yīng)該在這里指定:
    <ListItem value={number} />
  );
  return (
    <ul> {listItems} </ul>
  );
}

? 應(yīng)該寫成如下

function ListItem(props) {
  // 正確布轿!這里不需要指定 key :
  return <li>{props.value}</li>;
}

function NumberList(props) {
  const numbers = props.numbers;
  const listItems = numbers.map((number) =>
    // 正確哮笆!key 應(yīng)該在這里被指定
    <ListItem key={number.toString()}
              value={number} />

  );
  return (
    <ul>
      {listItems}
    </ul>
  );
}

const numbers = [1, 2, 3, 4, 5];
ReactDOM.render(
  <NumberList numbers={numbers} />,
  document.getElementById('root')
);
keys 在同輩元素中必須唯一

? 在數(shù)組中使用的 keys 必須在它們的同輩之間唯一来颤。然而它們并不需要全局唯一。我們可以在操作兩個(gè)不同數(shù)組的時(shí)候使用相同的 keys :

function Blog(props) {
  const sidebar = (
    <ul>
      {props.posts.map((post) =>
        <li key={post.id}>
          {post.title}
        </li>
      )}
    </ul>
  );
  const content = props.posts.map((post) =>
    <div key={post.id}>
      <h3>{post.title}</h3>
      <p>{post.content}</p>
    </div>
  );
  return (
    <div>
      {sidebar}
      <hr />
      {content}
    </div>
  );
}

const posts = [
  {id: 1, title: 'Hello World', content: 'Welcome to learning React!'},
  {id: 2, title: 'Installation', content: 'You can install React from npm.'}
];
ReactDOM.render(
  <Blog posts={posts} />,
  document.getElementById('root')
);

? 【注意】鍵是一個(gè)內(nèi)部映射稠肘,他不會(huì)作為props傳遞給組件內(nèi)部福铅,如果你需要在組件中使用到這個(gè)值,可以自定義一個(gè)屬性名將該值傳入到props中项阴,如下例中我們定義了一個(gè)id屬性傳入給props.

const content = posts.map((post) =>
  <Post
    key={post.id}
    id={post.id}
    title={post.title} />
);

? 在這個(gè)例子中本讥,我們能讀取props.id,但是讀取不了props.key

直接在JSX中使用map()

? 在上例中我們先聲明了一個(gè)listItem然后在jsx中引用鲁冯,然而我們也能在JSX中直接引用拷沸,稱之為 內(nèi)聯(lián)map()

function NumberList(props) {
  const numbers = props.numbers;
  return (
    <ul>
      { numbers.map((number) =>
        <ListItem key={number.toString()}
                  value={number} />
      )}
    </ul>
  );
}

? 至于選用哪種風(fēng)格編寫,只要遵循代碼清晰易讀原則即可
?

$ 表單

&esmp; HTML 表單元素與 React 中的其他 DOM 元素有所不同薯演,因?yàn)楸韱卧刈匀坏乇A袅艘恍﹥?nèi)部狀態(tài)撞芍。例如,這個(gè)純 HTML 表單接受一個(gè)單獨(dú)的 name:

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

? 該表單和 HTML 表單的默認(rèn)行為一致跨扮,當(dāng)用戶提交此表單時(shí)瀏覽器會(huì)打開一個(gè)新頁(yè)面序无。如果你希望 React 中保持這個(gè)行為,也可以工作衡创。但是多數(shù)情況下帝嗡,用一個(gè)處理表單提交并訪問(wèn)用戶輸入到表單中的數(shù)據(jù)的 JavaScript 函數(shù)也很方便。實(shí)現(xiàn)這一點(diǎn)的標(biāo)準(zhǔn)方法是使用一種稱為“受控組件(controlled components)”的技術(shù)璃氢。
?

受控組件(Controlled Components)

? 在 HTML 中哟玷,表單元素如 <input><textarea><select> 表單元素通常保持自己的狀態(tài)一也,并根據(jù)用戶輸入進(jìn)行更新巢寡。而在 React 中,可變狀態(tài)一般保存在組件的 state(狀態(tài)) 屬性中椰苟,并且只能通過(guò) setState()更新抑月。

? 通過(guò)使 React 的 state 成為 “單一數(shù)據(jù)源原則” 來(lái)結(jié)合這兩個(gè)形式。然后渲染表單的 React 組件也可以控制用戶輸入之后的行為舆蝴。這種形式谦絮,其值由 React 控制的輸入表單元素稱為“受控組件”。

class NameForm extends React.Component {
    constructor(props) {
        super(props);
        this.state = {value: ''};
    }
    
    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.bind(this)}>
                <label>
                    Name:
                    <input type="text" value={this.state.value} 
                           onChange={this.handleChange.bind(this} />
                </label>
                <input type="submit" value="Submit" />
            </form>
        )
    }
}

? 設(shè)置表單元素的value屬性之后洁仗,其顯示值將由this.state.value決定层皱,以滿足react狀態(tài)的同一個(gè)數(shù)據(jù)理念。每次鍵盤敲擊之后會(huì)執(zhí)行handleChange方法以便更新React狀態(tài)京痢,顯示只也將隨著用戶的輸入而改變奶甘。

? 由于value屬性設(shè)置在我們的表單元素上,顯示的值總是this.state.value祭椰,以滿足state 狀態(tài)的同意數(shù)據(jù)理念臭家。由于 handleChange 在每次敲擊鍵盤時(shí)運(yùn)行疲陕,以更新React state,顯示的值將更新為用戶的輸入

? 對(duì)于受控組件來(lái)說(shuō)钉赁,每一次state的變化都會(huì)伴有相關(guān)聯(lián)的處理函數(shù)蹄殃。這使得可以直接修改或驗(yàn)證用戶的輸入。比如你踩,我們希望強(qiáng)制name的輸入都是大寫字母诅岩,可以如下實(shí)現(xiàn)

handleChange(event) {
    this.setState({value: event.target.value.toUpperCase()});
}
textarea標(biāo)簽

? 在 HTML 中,<textarea> 元素通過(guò)它的子節(jié)點(diǎn)定義了它的文本值:

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

? 在 React 中带膜,<textarea> 的賦值使用 value 屬性替代吩谦。這樣一來(lái),表單中 <textarea> 的書寫方式接近于單行文本輸入框 :

class EssayForm extends React.Component {
    constructor(props) {
        super(props);
        this.state = {
            value: 'Please write an esay about your favorite DOM element.'
        }
    }
    // ...
    render() {
        return (
            <form  onSubmit={this.handle}>
                <label>
                    Name:
                    <textarea value={this.state.value} onChange={this.handleChange} />
                </label>
                <input type="submit" value="Submit" />
            </form>
        )
    }
}

? 注意膝藕,this.state.value 在構(gòu)造函數(shù)中初始化式廷,所以這些文本一開始就出現(xiàn)在文本域中。

select 標(biāo)簽

? 在 HTML 中芭挽,<select> 創(chuàng)建了一個(gè)下拉列表用法如下

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

? html利用selected默認(rèn)選中滑废,但在React中,不使用selected袜爪,而是給<select>標(biāo)簽中增加一個(gè)value屬性蠕趁,這使得受控組件使用更加方便,因?yàn)槟阒恍枰乱惶幾兞考纯伞?/p>

class FlavorForm extends React.Component {
  // ...
  render() {
    return (
      <form onSubmit={this.handleSubmit.bind(this)}>
        <label>
          Pick your favorite La Croix flavor:
          <select value={this.state.value} onChange={this.handleChange.bind(this)}>
              <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>
    );
  }
}

? 總的來(lái)說(shuō)辛馆,這使 <input type="text">俺陋, <textarea><select> 都以類似的方式工作 —— 它們都接受一個(gè) value 屬性可以用來(lái)實(shí)現(xiàn)一個(gè)受控組件。

多選select

? 使用多選select時(shí)怀各,需要給select標(biāo)簽增加value屬性倔韭,同時(shí)給value屬性賦值一個(gè)數(shù)組

<select multiple={true} value={['B', 'C']}>

?

# 利用e.target合并多個(gè)輸入元素的處理事件

? 當(dāng)您需要處理多個(gè)受控的 input 元素時(shí),您可以為每個(gè)元素添加一個(gè) name 屬性瓢对,并且讓處理函數(shù)根據(jù) event.target.name 的值來(lái)選擇要做什么。

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>
        
        <label>
          Number of guests:
          <input name="numberOfGuests" type="number" value={this.state.numberOfGuests}
            onChange={this.handleInputChange} />
        </label>
      </form>
    );
  }
}

ReactDOM.render(
  <Reservation />,
  document.getElementById('root')
);

? 注意這里使用ES6計(jì)算的屬性名稱語(yǔ)法來(lái)更新與給定輸入名稱相對(duì)應(yīng)的 state(狀態(tài)) 鍵的辦法

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

?

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

? 由于 setState() 自動(dòng)將部分狀態(tài)合并到當(dāng)前狀態(tài)胰苏,所以我們只需要調(diào)用更改的部分即可硕蛹。

受控 Input 組件的 null 值

?在 受控組件上指定值 prop 可防止用戶更改輸入,除非您希望如此硕并。 如果你已經(jīng)指定了一個(gè) value 法焰,但是輸入仍然是可編輯的,你可能會(huì)意外地把 value 設(shè)置為undefinednull 倔毙。

? 以下代碼演示了這一點(diǎn)埃仪。 (輸入首先被鎖定,但在短暫的延遲后可以編輯陕赃。)

ReactDOM.render(<input value="hi" />, mountNode);

setTimeout(function() {
  ReactDOM.render(<input value={null} />, mountNode);
}, 1000);

?

$ 狀態(tài)提升 (Lifting State Up)

? 通常情況下卵蛉,同一個(gè)數(shù)據(jù)的變化需要幾個(gè)不同的組件來(lái)反映颁股。我們建議提升共享的狀態(tài)到它們最近的祖先組件中。為了更好的理解傻丝,從一個(gè)案例來(lái)分析

溫度計(jì)算器

? 在本案例中甘有,我們采用自下而上的方式來(lái)創(chuàng)建一個(gè)溫度計(jì)算器,用來(lái)計(jì)算在一個(gè)給定溫度下水是否會(huì)沸騰(水溫是否高于100C)

(1)創(chuàng)建一個(gè) BoilingVerdict 組件葡缰,用來(lái)判水是否會(huì)沸騰并打印

function Bioling Verdict(props) {
  if (props.celsius >= 100) {
    return <p>The water would boil.</p>
  }
  return <p>The water would not boil.</p>
}

(2)有了判斷溫度的組件之后亏掀,我們需要一個(gè)Calculator組件,他需要包含一個(gè)<input>提供我們輸入文圖泛释,并在this.state.temperature中保存值滤愕。另外,以上BoilingVerdict 組件將會(huì)獲取到該輸入值并進(jìn)行判斷

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

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

  render() {
    const temperature = this.state.temperature;
    return (
      <fieldset>
        <legend>Enter temperature in Celsius;</legend>
        <input 
          value={temperature} 
          onChange={this.handleChange.bind(this)} />

        <BoilingVerdict celsius={parseFloat(templature)} />
      </fieldset>
    )
  }
}

(3)現(xiàn)在我們實(shí)現(xiàn)了基礎(chǔ)的父子組件通信功能怜校,假設(shè)我們有這樣的需求:除了一個(gè)設(shè)施文圖的輸入之外该互,還需要有一個(gè)華氏溫度輸入,并且要求兩者保持自動(dòng)同步

? 我們從Calculator中提取出TemperatureInput韭畸,然后增加新的scale屬性宇智,值可能是cf

const scaleNames = {
  c: 'Celsius',
  f: 'Fahrenheit'
}

class TemperatureInput extends React.Component {
  constructor(props) {
    super(props);
    this.state  = { temperature: e.target.value }
  }
  
  handleChange(e) {
    this.setState({ temperature: e.target.value });
  }

  render() {
    const temperature = this.state.temperature;
    const scale = this.props.scale;
    return (
      <fieldset>
        <legend>Enter temperature in {scaleNames[scale]}</legend>
        <input 
          value={temperature] 
          onChange={this.handleChange} />
      </fieldset>
    )
  }
}

? 抽出TemperatureInput之后,Calculator組件如下

class Calculator extends React.Component {
  render() {
    return (
      <div>
        <TemperatureInput scale="c" />
        <TemperatureInput scale="f" />
      </div>
    );
  }
}

? 現(xiàn)在有了兩個(gè)輸入框胰丁,但這兩個(gè)組件是獨(dú)立存在的随橘,不會(huì)互相影響,也就是說(shuō)锦庸,輸入其中一個(gè)溫度另一個(gè)并不會(huì)改變机蔗,與需求不符

? 我們不能再Calculator中顯示BoilingVerdict, Calcultor不知道當(dāng)前的溫度,因?yàn)樗窃?code>TemperatureInput 中隱藏的, 因此我們需要編寫轉(zhuǎn)換函數(shù)

(4)編寫轉(zhuǎn)換函數(shù)
? 我們先來(lái)實(shí)現(xiàn)兩個(gè)函數(shù)在攝氏度和華氏度之間轉(zhuǎn)換

function toCelsius(fahrenheit) {
  return (fahrenheit - 32) * 5 / 9;
}

functin toFahrenheit(celsius0 {
  return (celsius * 9 / 5) + 32;
}

?接下來(lái)甘萧,編寫函數(shù)用來(lái)接收一個(gè)字符串temperature 和一個(gè)轉(zhuǎn)化器函數(shù)作為參數(shù)萝嘁,并返回一個(gè)字符串,這個(gè)函數(shù)在兩個(gè)輸入之間進(jìn)行相互轉(zhuǎn)換扬卷。為了健壯性牙言,對(duì)于無(wú)效的temperature值,返回一個(gè)空字符串怪得,輸出保留三位小數(shù)

function tryConvert(temperature, convert) {
  const input = parseFloat(temperature);
  if (Number.isNaN(input)) {
    return '';
  }
  const output = convert(input);
  cosnt rounded = Math.round(output * 1000) / 1000;
  return rounded.toString();
}

? 其中咱枉,convert取值為 toCelsiustoFahrenheit

狀態(tài)提升

? 目前,兩個(gè) TempetureInput 組件都將其值保留在本地狀態(tài)中徒恋,但是我們希望這兩個(gè)輸入時(shí)相互同步的蚕断。但我們更新攝氏溫度輸入時(shí),華氏溫度輸入應(yīng)該反映并自動(dòng)更新入挣,反之亦然亿乳。

? 在React 中,共享state(狀態(tài))是通過(guò)將其移動(dòng)到需要的的組件的最接近的共同祖先組件來(lái)實(shí)現(xiàn)的径筏,這被稱之為狀態(tài)提升(Lifting State Up)葛假。我們將從TemperatureInput中移除相關(guān)狀態(tài)本地狀態(tài)障陶,并將其移動(dòng)到Calculator

? 如果Calculator擁有共享狀態(tài),那么他將成為兩個(gè)輸入當(dāng)前溫度的單一數(shù)據(jù)源桐款。他可以指示他們具有彼此一致的值 咸这。由于兩個(gè)TemperatureInput的組件的props來(lái)自于同一個(gè)父級(jí)Calculator組件,連個(gè)輸入將始終保持同步

? 讓我們來(lái)一步步實(shí)現(xiàn)這個(gè)過(guò)程

(1)將值挪出組件魔眨,用props傳入

render() {
  // const temperature = this.state.temperature;
  const temperature = this.props.temperature;
}

? 我們知道媳维,props是只讀的,因此我們不能根據(jù)子組件調(diào)用this.setState()來(lái)改變它遏暴。這個(gè)問(wèn)題侄刽,在React中通常使用 受控的方式來(lái)解決。就像DOM<input>一樣接收一個(gè)valueonChange prop朋凉, 所以可以定制Temperature 接受來(lái)自其腹肌 CalculatortemperatureonTemperatureChange:

thandleChange(e) {
   // 之前是:this.setState({ temperature: e.target.value });
  this.props.onTemperatureChange(e.target.value);
}

請(qǐng)注意州丹,之定義組件中的 templatureonTemperatureChange prop(屬性)名稱沒(méi)有特殊的含義。我們可以命名為任何其他名字杂彭,就像命名他們?yōu)?code>value和onChange墓毒。是一個(gè)和常見的慣例

? onTemperatureChange proptemperature prop 一起由父級(jí)的Calculator組件提供,他將通過(guò)修改自己的本地state來(lái)處理數(shù)據(jù)變更亲怠,從而通過(guò)新值重新渲染兩個(gè)輸入∷疲現(xiàn)在我們的代碼如下

class TemperatureInput extends React.Component {
  constructor(props) {
    super(props);
  }

  handleChange(e) {
    this.props.onTemperatureChange(e.target.value);
  }

  render() {
    const  temperature = this.props.temperature;
    const scale< this.prps.scale;
    return (
      <fieldset>
        <legend>Enter temperature in {scalenames[scale]}</legend>
        <input value={tempearature}  onChange={this.handleChange.bind(this)} />
      </fieldset>
    )
  }
}

? 我們將當(dāng)前輸入的 temperaturescale存儲(chǔ)在本地的state中,這是我們沖輸入“提升”的state(狀態(tài))团秽,他將作為連個(gè)輸入的“單一數(shù)據(jù)源”主胧。為了渲染這兩個(gè)輸入,我們需要知道的所有數(shù)據(jù)的最小表示习勤,如攝氏度輸入37踪栋,這時(shí)Calculator組件狀態(tài)將是:

{
  temperature: '37',
  scale: 'c'
}

? 我們確實(shí)可以存儲(chǔ)兩個(gè)輸入框(攝氏度和華氏度)的值,但事實(shí)證明是不必要的图毕。我們只要存儲(chǔ)最近更改的輸入框的值夷都,以及他們所表示的度量衡(scale)就足夠了。然后推斷出另一個(gè)值吴旋。這也是我們實(shí)現(xiàn)兩個(gè)輸入框保持同步的途徑

class Calculator extends React.Component {
  constructor(props) {
    super(props);
    this.state = { temperature: '',  scale: 'c' }
  }

  handleCelsiusChange(temperature) {
    this.setState({ scale: 'c', temperature });
  }
  
  handleRahrenheitChange(temperature) {
    this.setState({scale: 'f', temperature })
  }

  render() {
    const scale = this.state.scale;
    const temperature = this.state.temperature;
    const celsius = scale === 'f' ? tryConvert(temperature, toCelsius) : temperature;
    const fahrenheit = scale === 'c' ? tryConvert(temperature, toFahrenheit) : temperature;

    return (
      <div>
        <TemperatureInput 
          scale="c" 
          temperature={celsuis} 
          onTemperatureChange={this.handleCelsiusChange.bind(this)} />

        <TemperatureInput 
          scale="f" 
          temperature={fahrenheit} 
          onTemperatureChange={this.handleFahrenheitChange.bind(this)} />

        <BiolingVerdict celsius={parseFloat(celsius)} />
      </div>
    )
  }
}

? 現(xiàn)在损肛,無(wú)論你編輯哪個(gè)輸入框,Calculator 中的 this.state.temperaturethis.state.scale 都會(huì)更新荣瑟。其中一個(gè)輸入框獲取值,所以任何用戶輸入都被保留摩泪,并且另一個(gè)輸入總是基于它重新計(jì)算值笆焰。

? 讓我們回顧一下編輯輸入時(shí)會(huì)發(fā)生什么:

  • React 調(diào)用在 DOM <input> 上的 onChange 指定的函數(shù)。在我們的例子中见坑,這是 TemperatureInput 組件中的 handleChange 方法嚷掠。
  • TemperatureInput 組件中的handleChange 方法使用 新的期望值 調(diào)用 this.props.onTemperatureChange()捏检。TemperatureInput 組件中的props(屬性) ,包括 onTemperatureChange不皆,由其父組件 Calculator 提供贯城。
  • 當(dāng)它預(yù)先呈現(xiàn)時(shí), Calculator 指定了攝氏 TemperatureInputonTemperatureChangeCalculatorhandleCelsiusChange 方法霹娄,并且華氏 TemperatureInputonTemperatureChangeCalculatorhandleFahrenheitChange 方法能犯。因此,會(huì)根據(jù)我們編輯的輸入框犬耻,分別調(diào)用這兩個(gè) Calculator 方法踩晶。
  • 在這些方法中, Calculator 組件要求 React 通過(guò)使用 新的輸入值 和 剛剛編輯的輸入框的當(dāng)前度量衡 來(lái)調(diào)用 this.setState() 來(lái)重新渲染自身
  • React 調(diào)用 Calculator 組件的 render 方法來(lái)了解 UI 外觀應(yīng)該是什么樣子枕磁《沈撸基于當(dāng)前溫度和激活的度量衡來(lái)重新計(jì)算兩個(gè)輸入框的值。這里進(jìn)行溫度轉(zhuǎn)換
  • React 使用 Calculator 指定的新 props(屬性) 調(diào)用各個(gè) TemperatureInput 組件的 render 方法计济。 它了解 UI 外觀應(yīng)該是什么樣子
  • React DOM 更新 DOM 以匹配期望的輸入值茸苇。我們剛剛編輯的輸入框接收當(dāng)前值,另一個(gè)輸入框更新為轉(zhuǎn)換后的溫度沦寂。

^狀態(tài)提升經(jīng)驗(yàn)總結(jié)

? 在一個(gè) React 應(yīng)用中学密,對(duì)于任何可變的數(shù)據(jù)都應(yīng)該遵循“單一數(shù)據(jù)源”原則,通常情況下凑队,state首先被添加到需要它進(jìn)行渲染的組件则果,然后如果其他的組件也需要它,你可以提升狀態(tài)到他們最近的祖先組件漩氨。你應(yīng)該依賴從上到下的數(shù)據(jù)流向西壮,而不是試圖在不同的組件中同步狀態(tài)。

? 提升狀態(tài)相對(duì)于雙向綁定方法需要寫更多的"模板"代碼叫惊,但是有個(gè)好處款青,他可以更方便的找到和隔離bugs。由于熱河state(狀態(tài))都"存活"若干個(gè)組件中霍狰,而且可以分別對(duì)其獨(dú)立修改抡草,所以發(fā)生錯(cuò)誤的可能性大大減少。另外蔗坯,你可以實(shí)現(xiàn)任何定制的邏輯來(lái)拒絕或者轉(zhuǎn)換用戶輸入康震。

? 如果某個(gè)東西可以從props(屬性)或者state(狀態(tài))得到,那么他可能不應(yīng)該在state中宾濒。例如我們只保存最后編輯的temperaturescale腿短,而不是保存celsiusValuefahrenheitValue。另一個(gè)輸入框的值總是在render()中計(jì)算得到。這是我們對(duì)其進(jìn)行清除和四舍五入到其他字段的同事不會(huì)丟失其精度

? 當(dāng)你看到UI中的錯(cuò)誤橘忱,你可以使用React開發(fā)者工具來(lái)檢查props赴魁,并向上遍歷樹,知道找到負(fù)責(zé)更新狀態(tài)的組件钝诚,這是你可以跟蹤到bug的源頭:Monitoring State in React DevTools
?

$ 組合 VS 繼承

? 組合 Composition vs 繼承Inheritance

? React 擁有一個(gè)強(qiáng)大的組合模型颖御,建議使用組合而不是繼承以實(shí)現(xiàn)代碼的重用

? 接下來(lái)同樣從案例觸發(fā)來(lái)考慮幾個(gè)問(wèn)題,新手一般會(huì)用繼承凝颇,然后這里推薦使用組合

包含

? 一些組件在設(shè)計(jì)前無(wú)法或者自己要使用什么子組件潘拱,尤其是在 SidebarDialog 等通用的 “容器” 中比較常見

? 這種組件建議使用特殊的children prop 來(lái)直接傳遞子元素到他們的輸出中:

function FancyBorder(props) {
  return (
    <div className={'FancyBorder FancyBorder-' + props.color}>
         // children 表示來(lái)自父組件中的子元素
        {props.children}
    </div>
  )
}

? 這允許其他組件通過(guò)嵌套JSX傳遞任意子組件給他們,比如在父組件中有h1p子元素

function WelcomeDialog() {
    return (
        <FancyBo9rder color="blue">
            <h1 className="Dialog-title">Welcome</h1>
            <p className="Dialog-message">Thank you for your visitiong</p>
        </FancBorder>
    )
}

? 在 <FancyBorder> JSX 標(biāo)簽中的任何內(nèi)容被傳遞到FancyBorder 組件中祈噪,作為一個(gè) children prop(屬性)泽铛。由于 FancyBorder 渲染{props.children} 到一個(gè) <div> 中,傳遞的元素會(huì)呈現(xiàn)在最終的輸出中辑鲤。

? 這是一種簡(jiǎn)單的用法盔腔,這種案例并不常見,有時(shí)候我們需要在一個(gè)組件中有多個(gè)“占位符”月褥,這種情況下弛随,你可以使用自定義的prop屬性,而不是children:

function Contacts() {
  return <div className="Contacts" />;
}

function Chat() {
  return <div className="Chat" />;
}

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 />
      }
    />
  )
}

? 如 <Contacts /><Chat />React 元素本質(zhì)上也是對(duì)象宁赤,所以可以將其像其他數(shù)據(jù)一樣作為 props(屬性) 傳遞使用舀透。

特例

? 有時(shí)候,我們考慮組件作為其它組件的“特殊情況”决左。例如愕够,我們可能說(shuō)一個(gè) WelcomeDialogDialog 的一個(gè)特殊用例。
? 在React中佛猛,也可以使用組合來(lái)實(shí)現(xiàn)惑芭,一個(gè)偏“特殊”的組件渲染出一個(gè)偏“通用”的組件,通過(guò) props(屬性) 配置它:

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

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

function WelcomeDialog() {
    return (
        <Dialog title="Welcome" message="Thank you for your visiting">
    )
}

? 這對(duì)于類定義的組件組合也同樣適用

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

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}!`);
  }
}

?

如何看待繼承继找?

在 Facebook 遂跟,在千萬(wàn)的組件中使用 React,我們還沒(méi)有發(fā)現(xiàn)任何用例婴渡,值得我們建議你用繼承層次結(jié)構(gòu)來(lái)創(chuàng)建組件纤控。

? 使用 props(屬性) 和 組合已經(jīng)足夠靈活來(lái)明確才顿、安全的定制一個(gè)組件的外觀和行為糕珊。切記崖堤,組件可以接受任意的 props(屬性) ,包括原始值柠并、React 元素究飞,或者函數(shù)

? 如果要在組件之間重用非 U I功能置谦,我們建議將其提取到單獨(dú)的 JavaScript 模塊中堂鲤。組件可以導(dǎo)入它并使用該函數(shù)亿傅,對(duì)象或類,而不擴(kuò)展它瘟栖。
?

$ 后語(yǔ)

? React的顛覆性思想不同于之前的任何一個(gè)框架葵擎,掌握React這門技術(shù),會(huì)幫助你自己思考如何更高性能半哟、高效率的編程酬滤,這可能影響你方方面面和以后的任意一次編程經(jīng)歷。

? 本文中如有錯(cuò)誤之處寓涨,歡迎指正盯串。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市戒良,隨后出現(xiàn)的幾起案子体捏,更是在濱河造成了極大的恐慌,老刑警劉巖糯崎,帶你破解...
    沈念sama閱讀 212,884評(píng)論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件几缭,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡沃呢,警方通過(guò)查閱死者的電腦和手機(jī)年栓,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,755評(píng)論 3 385
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)薄霜,“玉大人某抓,你說(shuō)我怎么就攤上這事《韫希” “怎么了否副?”我有些...
    開封第一講書人閱讀 158,369評(píng)論 0 348
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)鸵熟。 經(jīng)常有香客問(wèn)我副编,道長(zhǎng),這世上最難降的妖魔是什么流强? 我笑而不...
    開封第一講書人閱讀 56,799評(píng)論 1 285
  • 正文 為了忘掉前任痹届,我火速辦了婚禮,結(jié)果婚禮上打月,老公的妹妹穿的比我還像新娘队腐。我一直安慰自己,他們只是感情好奏篙,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,910評(píng)論 6 386
  • 文/花漫 我一把揭開白布柴淘。 她就那樣靜靜地躺著迫淹,像睡著了一般。 火紅的嫁衣襯著肌膚如雪为严。 梳的紋絲不亂的頭發(fā)上敛熬,一...
    開封第一講書人閱讀 50,096評(píng)論 1 291
  • 那天,我揣著相機(jī)與錄音第股,去河邊找鬼应民。 笑死,一個(gè)胖子當(dāng)著我的面吹牛夕吻,可吹牛的內(nèi)容都是我干的诲锹。 我是一名探鬼主播,決...
    沈念sama閱讀 39,159評(píng)論 3 411
  • 文/蒼蘭香墨 我猛地睜開眼涉馅,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼归园!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起稚矿,我...
    開封第一講書人閱讀 37,917評(píng)論 0 268
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤庸诱,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后盐捷,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體偶翅,經(jīng)...
    沈念sama閱讀 44,360評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,673評(píng)論 2 327
  • 正文 我和宋清朗相戀三年碉渡,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了聚谁。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,814評(píng)論 1 341
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡滞诺,死狀恐怖形导,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情习霹,我是刑警寧澤朵耕,帶...
    沈念sama閱讀 34,509評(píng)論 4 334
  • 正文 年R本政府宣布,位于F島的核電站淋叶,受9級(jí)特大地震影響阎曹,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜煞檩,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 40,156評(píng)論 3 317
  • 文/蒙蒙 一处嫌、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧斟湃,春花似錦熏迹、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,882評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)坛缕。三九已至,卻和暖如春捆昏,著一層夾襖步出監(jiān)牢的瞬間赚楚,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,123評(píng)論 1 267
  • 我被黑心中介騙來(lái)泰國(guó)打工屡立, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留直晨,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 46,641評(píng)論 2 362
  • 正文 我出身青樓膨俐,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親罩句。 傳聞我的和親對(duì)象是個(gè)殘疾皇子焚刺,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,728評(píng)論 2 351

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