React 項(xiàng)目實(shí)踐——搭建一個(gè)溫度控制 App
我們將搭建一個(gè)溫度控制 App,幫助初學(xué)者學(xué)習(xí) React state hook、handle 事件等等。

嘗試自己搭建
如果你想先自己試著寫這個(gè)項(xiàng)目,可以對照下列項(xiàng)目需求(你也可以參考下方的初始代碼):
當(dāng)用戶點(diǎn)擊“+”按鈕的時(shí)候,溫度上升 溫度不能高于 30℃ 當(dāng)用戶點(diǎn)擊“-”按鈕的時(shí)候,溫度降低 溫度不能低于 0℃ 當(dāng)溫度高于 15℃ 的時(shí)候,背景色變成紅色(我創(chuàng)建了一個(gè)樣式“hot”) 當(dāng)溫度高于 15℃ 的時(shí)候,背景色變成藍(lán)色(我創(chuàng)建了一個(gè)樣式“cold”)
初始代碼
注:本文默認(rèn)你已經(jīng)安裝好 React 開發(fā)環(huán)境。
首先在終端運(yùn)行 ?create-react-app:
npx?create-react-app?temperature-control
同時(shí)在 VS Code(或者別的編輯器)打開項(xiàng)目。刪除 ?index.js 里的內(nèi)容,然后將以下代碼粘貼進(jìn)這個(gè)文件:
import?React?from?'react';
import?ReactDOM?from?'react-dom';
import?'./index.css';
import?App?from?'./App';
ReactDOM.render(
????<React.StrictMode>
????????<App?/>
????React.StrictMode>,
????document.getElementById('root')
);
同樣,刪除 ?index.css 的內(nèi)容,粘貼以下內(nèi)容:
body?{
????font-family:?sans-serif;
????text-align:?center;
????display:?flex;
????flex-direction:?column;
????justify-content:?center;
????align-items:?center;
????text-align:?center;
????min-height:?100vh;
}
.app-container?{
????height:?400px;
????width:?300px;
????background:?#2b5870;
????border-radius:?20px;
????box-shadow:?10px?10px?38px?0px?rgba(0,?0,?0,?0.75);
}
.temperature-display-container?{
????display:?flex;
????justify-content:?center;
????align-items:?center;
????height:?70%;
}
.temperature-display?{
????display:?flex;
????border-radius:?50%;
????color:?#ffffff;
????height:?220px;
????width:?220px;
????text-align:?center;
????justify-content:?center;
????align-items:?center;
????font-size:?48px;
????border:?3px?#ffffff?solid;
????transition:?background?0.5s;
}
button?{
????border-radius:?100px;
????height:?80px;
????width:?80px;
????font-size:?32px;
????color:?#ffffff;
????background:?rgb(105,?104,?104);
????border:?2px?#ffffff?solid;
}
button:hover?{
????background:?rgb(184,?184,?184);
????cursor:?pointer;
}
button:focus?{
????outline:?0;
}
.button-container?{
????display:?flex;
????justify-content:?space-evenly;
????align-items:?center;
}
.neutral?{
????background:?rgb(184,?184,?184);
}
.cold?{
????background:?#035aa6;
}
.hot?{
????background:?#ff5200;
}
最后,刪除 ?App.js 的內(nèi)容,粘貼以下代碼:
import?React?from?'react';
const?App?=?()?=>?{
????return?(
????????<div?className='app-container'>
????????????<div?className='temperature-display-container'>
????????????????<div?className='temperature-display'>10°Cdiv>
????????????div>
????????????<div?className='button-container'>
????????????????<button>+button>
????????????????<button>-button>
????????????div>
????????div>
????);
};
export?default?App;
現(xiàn)在我們可以在 VS Code 打開終端,運(yùn)行:
npm?start
如果一切無誤,將顯示:

好棒!接下來我們可以基于這個(gè)模版搭建了,不用擔(dān)心 CSS 部分。
動(dòng)態(tài)顯示溫度值——使用 ?State
首先我們讓溫度值動(dòng)態(tài)顯示。將溫度值存儲(chǔ)在 state 內(nèi),便于我們稍后讀取數(shù)據(jù),并用于邏輯呈現(xiàn)。
建議把引起 ?UI 改變的東西都放在 state 里。
在 ?App.js ?文件開頭導(dǎo)入 ?useState ?hook:
import?React,?{?useState?}?from?'react';
在 ?App function 函數(shù)內(nèi)添加:
const?[temperatureValue,?setTemperatureValue]?=?useState(10);
我們通過 ?useState 進(jìn)行組件狀態(tài)管理。useState ?hook 包含兩個(gè)參數(shù):
一個(gè)表示狀態(tài)初始值的變量 一個(gè)更新狀態(tài)值的函數(shù)
在這個(gè)例子中,我們調(diào)用了狀態(tài)變量 ?temperatureValue 和函數(shù) ?setTemperatureValue,將 10 這個(gè)值傳遞給 useState hook,作為 temperatureValue 的初始值。
現(xiàn)在我們把這個(gè)狀態(tài)值用到代碼里。記住了,我們從 ?useState ?獲取的值的用法和其他 JavaScript 變量和函數(shù)的用法一樣。
將 JSX 里的固定的溫度值改為狀態(tài)變量。這是原來的值:
<div?className='temperature-display'>10°Cdiv>
改成這樣:
<div?className='temperature-display'>{temperatureValue}°Cdiv>
注意我們使用 ?{} ?來渲染 ?temperatureValue ?變量?,F(xiàn)在,如果溫度值改變,組件將重新渲染,顯示新的溫度值。
App.js ?文件目前是這樣的:
import?React,?{?useState?}?from?'react';
const?App?=?()?=>?{
????const?[temperatureValue,?setTemperatureValue]?=?useState(10);
????return?(
????????<div?className='app-container'>
????????????<div?className='temperature-display-container'>
????????????????<div?className='temperature-display'>{temperatureValue}°Cdiv>
????????????div>
????????????<div?className='button-container'>
????????????????<button>+button>
????????????????<button>-button>
????????????div>
????????div>
????);
};
export?default?App;
現(xiàn)在,如果你運(yùn)行 app,瀏覽器中的一切好像跟之前一樣。
但是,如果你將傳遞給 useState hook 的初始值從 10 改為其他(比如 15),你會(huì)看到 app 更新了,也就是說狀態(tài)鉤子起作用了!
按鍵時(shí)更改狀態(tài)
接下來,我們要在按按鈕時(shí)升高或降低溫度。
useState hook 有一個(gè) ?setTemperatureValue ?函數(shù),可以修改溫度值,所以我們可以在按鈕的 ?onClick ?事件中用到它。
首先把“+”按鈕的代碼修改成:
?<button?onClick={()?=>?setTemperatureValue(temperatureValue?+?1)}>+button>
注意它是怎么調(diào)用 ?setTemperatureValue ?函數(shù)的。獲得當(dāng)前溫度值,加上 1,然后將其作為參數(shù)傳遞。
因?yàn)闇囟瘸跏贾凳?10,加上 1 的話狀態(tài)值就變成 11。再按一次按鈕,狀態(tài)值變成 12......
將“-”按鈕的代碼修改成:
?<button?onClick={()?=>?setTemperatureValue(temperatureValue?-?1)}>-button>
和對“+”按鈕的操作類似,不過這次是降低溫度值。
現(xiàn)在我們的代碼是這樣的:
import?React,?{?useState?}?from?'react';
const?App?=?()?=>?{
????const?[temperatureValue,?setTemperatureValue]?=?useState(10);
????return?(
????????<div?className='app-container'>
????????????<div?className='temperature-display-container'>
????????????????<div?className='temperature-display'>{temperatureValue}°Cdiv>
????????????div>
????????????<div?className='button-container'>
????????????????<button?onClick={()?=>?setTemperatureValue(temperatureValue?+?1)}>+button>
????????????????<button?onClick={()?=>?setTemperatureValue(temperatureValue?-?1)}>-button>
????????????div>
????????div>
????);
};
export?default?App;
試著在瀏覽器運(yùn)行代碼,點(diǎn)擊按鈕,溫度值會(huì)升高或降低。
基于狀態(tài)修改顏色
接下來我們做點(diǎn)有意思的東西——根據(jù)溫度的高低顯示不同的背景色。
如果溫度是 15℃ 或以上,背景色是紅色;反之,背景色是藍(lán)色。
在 CSS 里,我寫了這兩個(gè)類:
.cold?將背景色設(shè)置為藍(lán)色.hot?將背景色設(shè)置為紅色
將其中一個(gè)類添加至 ?temperature display ?div,會(huì)改變背景色,比如:
<div?className='temperature-display?cold'>{temperatureValue}°Cdiv>
背景色是藍(lán)色
<div?className='temperature-display?hot'>{temperatureValue}°Cdiv>
背景色是紅色
那么,怎么基于狀態(tài)動(dòng)態(tài)地應(yīng)用這兩個(gè)類呢?
創(chuàng)建另一個(gè)狀態(tài)鉤子,存放 ?temperatureColor:
const?[temperatureColor,?setTemperatureColor]?=?useState('cold');
注意我們給 ?temperatureColor ?狀態(tài)對象設(shè)置初始值為 “cold”(因?yàn)槌跏紲囟戎禐?10,我們希望背景色是藍(lán)色)。
然后我們使用模板常量動(dòng)態(tài)地添加需要的類:
<div?className={`temperature-display?${temperatureColor}`}>{temperatureValue}°Cdiv>
這樣一來,創(chuàng)建一個(gè)字符串,動(dòng)態(tài)應(yīng)用 ?temperatureColor ?變量。當(dāng) ?temperatureColor ?變成 “hot” 的時(shí)候,組件會(huì)重新渲染,給 className 字符串添加 “hot” 類。
我們的代碼目前是這樣的:
import?React,?{?useState?}?from?'react';
const?App?=?()?=>?{
????const?[temperatureValue,?setTemperatureValue]?=?useState(10);
????const?[temperatureColor,?setTemperatureColor]?=?useState('cold');
????return?(
????????<div?className='app-container'>
????????????<div?className='temperature-display-container'>
????????????????<div?className={`temperature-display?${temperatureColor}`}>{temperatureValue}°Cdiv>
????????????div>
????????????<div?className='button-container'>
????????????????<button?onClick={()?=>?setTemperatureValue(temperatureValue?+?1)}>+button>
????????????????<button?onClick={()?=>?setTemperatureValue(temperatureValue?-?1)}>-button>
????????????div>
????????div>
????);
};
export?default?App;
將初始 ?temperatureColor ?狀態(tài)變量改為 “hot” 或 “cold”,顯示板的背景色隨之改變。
我們已經(jīng)有一個(gè) ?onClick 事件可更改 temperatureValue 的值,現(xiàn)在我們給這個(gè)事件增加新的邏輯。
目前 ?onClick 事件有一個(gè)內(nèi)聯(lián)函數(shù)。對于單行函數(shù)來說用內(nèi)聯(lián)函數(shù)比較好。但是如果是有不同邏輯的多行函數(shù),最好是將函數(shù)放到 JSX 外面,讓代碼更清晰。
將下列代碼粘貼到狀態(tài)下面:
const?increaseTemperature?=?()?=>?{
????setTemperatureValue(temperatureValue?+?1);
};
const?decreaseTemperature?=?()?=>?{
????setTemperatureValue(temperatureValue?-?1);
};
這里我們定義了兩個(gè)函數(shù),用于升高或降低溫度。
接下來,修改按鈕的 ?onClick ?屬性,調(diào)用這些函數(shù):
?<button?onClick={increaseTemperature}>+button>
????<button?onClick={decreaseTemperature}>-button>
我們的代碼目前是這樣:
?import?React,?{?useState?}?from?'react';
const?App?=?()?=>?{
????const?[temperatureValue,?setTemperatureValue]?=?useState(10);
????const?[temperatureColor,?setTemperatureColor]?=?useState('cold');
????const?increaseTemperature?=?()?=>?{
????????setTemperatureValue(temperatureValue?+?1);
????};
????const?decreaseTemperature?=?()?=>?{
????????setTemperatureValue(temperatureValue?-?1);
????};
????return?(
????????<div?className='app-container'>
????????????<div?className='temperature-display-container'>
????????????????<div?className={`temperature-display?${temperatureColor}`}>{temperatureValue}°Cdiv>
????????????div>
????????????<div?className='button-container'>
????????????????<button?onClick={increaseTemperature}>+button>
????????????????<button?onClick={decreaseTemperature}>-button>
????????????div>
????????div>
????);
};
export?default?App;
注意其實(shí)沒啥改變,我們只是重新構(gòu)造了代碼,為接下來的工作做準(zhǔn)備。
現(xiàn)在就更容易為點(diǎn)擊按鈕事件添加邏輯了。
給 ?increaseTemperature ?函數(shù)添加邏輯:
?const?increaseTemperature?=?()?=>?{
????const?newTemperature?=?temperatureValue?+?1;
????setTemperatureValue(newTemperature);
????if?(newTemperature?>=?15)?{
????????setTemperatureColor('hot');
????}
};
當(dāng)我們點(diǎn)擊按鈕若干次,temperatureValue ?等于或大于 15℃ 時(shí),temperatureColor ?變量會(huì)更改,組件重新渲染,給顯示板添加 “hot” 類。
降低溫度時(shí)的邏輯是類似的:
?const?decreaseTemperature?=?()?=>?{
????const?newTemperature?=?temperatureValue?-?1;
????setTemperatureValue(newTemperature);
????if?(newTemperature?15)?{
????????setTemperatureColor('cold');
????}
};
app 最終的代碼如下:
?import?React,?{?useState?}?from?'react';
const?App?=?()?=>?{
????const?[temperatureValue,?setTemperatureValue]?=?useState(10);
????const?[temperatureColor,?setTemperatureColor]?=?useState('cold');
????const?increaseTemperature?=?()?=>?{
????????const?newTemperature?=?temperatureValue?+?1;
????????setTemperatureValue(newTemperature);
????????if?(newTemperature?>=?15)?{
????????????setTemperatureColor('hot');
????????}
????};
????const?decreaseTemperature?=?()?=>?{
????????const?newTemperature?=?temperatureValue?-?1;
????????setTemperatureValue(newTemperature);
????????if?(newTemperature?15)?{
????????????setTemperatureColor('cold');
????????}
????};
????return?(
????????<div?className='app-container'>
????????????<div?className='temperature-display-container'>
????????????????<div?className={`temperature-display?${temperatureColor}`}>{temperatureValue}°Cdiv>
????????????div>
????????????<div?className='button-container'>
????????????????<button?onClick={increaseTemperature}>+button>
????????????????<button?onClick={decreaseTemperature}>-button>
????????????div>
????????div>
????);
};
export?default?App;
運(yùn)行 App,檢查是不是一切 ok——太棒了!
掃碼關(guān)注公眾號(hào),訂閱更多精彩內(nèi)容。
