在程式開發過程常常會出現一種情況
一個屬性或狀態同時控制好幾個網頁元素
這個時候怎麼寫就會直接影響著網頁的運行效能
為了養成良好的習慣
今天就來談談應該要怎麼寫會比較好吧!
如果多個元素被一個因素影響
多個元素又各自獨立
那麼運行起來大家各自忙碌
造成許多額外的效能浪費
網頁的效能如果不好
使用者體驗就會變差
這並不是我們所期望的結果
因此
React 建議
當出現資料同時影響著多個組件(網頁元素)的時候
應該把這個資料提升到最接近這些組件的共同祖先
這麼一來當資料發生改變
就能夠一次更新那些需要被改變的組件
達到重複使用的精神
也讓程式變得更好管理
官網提供了一個 攝氏華氏溫度計 的範例來說明這件事情
不過任性如我當然就要自己想一個例子來做囉
所以說
我決定要來做個 公分公尺換算器 來記錄今天的學習!
要想做一個長度換算器
首先我們需要讓使用者輸入的欄位
import React from 'react';
import ReactDOM from 'react-dom';
class LengthInput extends React.Component{
constructor(){
super();
this.state = {
length : "",
}
this.handleChange = this.handleChange.bind(this);
}
handleChange(e){
this.setState({length: e.target.value});
}
render(){
return(
<fieldset>
<legend>公分</legend>
<input type="text" value={length} onChange = {this.handleChange} />
</fieldset>
);
}
}
export default LengthInput;
先產生一個讓使用者輸入長度的組件
裡面有一個叫做length
的狀態用來存放輸入的值handleChange
函式更改狀態為輸入的內容
執行結果:
由於至少要兩個欄位才能夠互相轉換
所以這邊要再新增一個輸入欄位
import React from 'react';
import ReactDOM from 'react-dom';
const scaleName = {
cm: '公分',
m: '公尺'
}
class LengthInput extends React.Component{
constructor(){
super();
this.state = {
length : "",
}
this.handleChange = this.handleChange.bind(this);
}
handleChange(e){
this.setState({length: e.target.value});
}
render(){
const length = this.state.length;
const scale = this.props.scale;
return(
<fieldset>
<legend>{scaleName[scale]}:</legend>
<input type="text"
value={length}
onChange = {this.handleChange} />
</fieldset>
);
}
}
class Caculator extends React.Component{
render(){
return(
<div>
<LengthInput scale='cm' />
<br />
<LengthInput scale='m' />
</div>
);
}
}
export default Caculator;
在這裡我們使用同一個LengthInput
組件來產生第二個輸入欄位
透過傳入的 props 來分辨兩個輸入欄位
並且建立一個新的組件作為他們的的父組件(parent)
也就是整個長度換算器的外觀了
執行結果:
接下來要讓單位轉換並且呈現在畫面上
所以我們需要寫轉換的function:
function toMeter(cm){
return cm / 100;
}
function toCentermeter(m){
return m * 100;
}
function doConvert(unit, convert){
const input = parsetFloat(unit);
if(isNaN(input))
return '';
const output = convert(input);
return output.toString();
}
toMeter
和toCentermeter
是基本的轉換函式
而doConvert
是因為要讓他們呼叫同一個函式
透過這個函式來判斷要如何轉換單位
參數unit
是用來存放長度的數值資料
而convert
則是應該要呼叫的轉換函式
寫到這邊會出現一個問題:
長度單位分別在兩個獨立的組件內部,要怎麼讓他們同步轉換呢?
除此之外
兩個組件明明使用的是同一份資料(長度)
卻各自持有一個獨立的狀態重複存放
讓帶有狀態的組件數量增加了一個
這樣造成網頁的效能下降
所以
接下來我們要把這兩個組件的狀態合併起來
讓兩種長度單位能同步轉換
同時實作今天的主要目標 - 提升狀態(lifting state up)
首先先調整LengthInput
的資料來源:
class LengthInput extends React.Component{
constructor(){
super();
this.handleChange = this.handleChange.bind(this);
}
handleChange(e){
// this.setState({length: e.target.value});
this.props.onLengthChange(e.target.value);
}
render(){
const length = this.props.length;
const scale = this.props.scale;
return(
<fieldset>
<legend>{scaleName[scale]}:</legend>
<input type="text"
value={length}
onChange = {this.handleChange} />
</fieldset>
);
}
}
這裡我們拿掉了組件的狀態資料 state
然後在handleChange
的部分
因為 props 是不能被更動的東西
所以必須呼叫父組件內部的函式
透過更新父組件的狀態來改變來自父組件的狀態資料
也就是const length = this.props.length;
這句程式碼的意義了
子組件處理完後
現在要來把狀態移動到他們最接近的共同祖先
也就是Calculator
這個組件啦
程式碼:
import React from 'react';
import ReactDOM from 'react-dom';
const scaleName = {
cm: '公分',
m: '公尺'
}
class LengthInput extends React.Component{
constructor(){
super();
this.handleChange = this.handleChange.bind(this);
}
handleChange(e){
// this.setState({length: e.target.value});
this.props.onLengthChange(e.target.value);
}
render(){
const length = this.props.length;
const scale = this.props.scale;
return(
<fieldset>
<legend>{scaleName[scale]}:</legend>
<input type="text"
value={length}
onChange = {this.handleChange} />
</fieldset>
);
}
}
function toMeter(cm){
return cm / 100;
}
function toCentermeter(m){
return m * 100;
}
function doConvert(unit, convert){
const input = parseFloat(unit);
if(isNaN(input))
return '';
const output = convert(input);
return output.toString();
}
class Calculator extends React.Component{
constructor(){
super();
this.state = {
length:"",
scale:"cm"
}
this.handleCentermeterChange = this.handleCentermeterChange.bind(this);
this.handleMeterChange = this.handleMeterChange.bind(this);
}
handleCentermeterChange(len){
this.setState({length:len , scale:'cm'});
}
handleMeterChange(len){
this.setState({length:len , scale:'m'});
}
render(){
const scale = this.state.scale;
const length = this.state.length;
const centermeter =
scale === 'm' ? doConvert(length, toCentermeter) : length;
const meter =
scale === 'cm' ? doConvert(length, toMeter) : length;
return(
<div>
<LengthInput scale='cm' length={centermeter} onLengthChange={this.handleCentermeterChange}/>
<br />
<LengthInput scale='m' length={meter} onLengthChange={this.handleMeterChange}/>
</div>
);
}
}
export default Calculator;
在這段程式碼中
我們把使用者輸入的長度值統一先記錄在Calculator
這個組件中
不論使用者輸入的位置是哪一個Calculator
都有對應的函式從子組件偵測並且回來呼叫父組件的函式
透過這個組件的狀態改變
同步傳遞 props 變動未被輸入的那個欄位的內容
執行結果:
透過今日這個任性的練習
簡單來說
狀態提升可以重點紀錄成:
- Eva Vue.js 30天隨身包
- Ben那些年,我們一起錯過的Javascript
- Ray激戰ReactJS 30天
Day15 end
by 瑞Ray ε≡ヘ( ´∀`)ノ