520看到女神這麼熱,我不禁用React寫了個夏日空調

語言: CN / TW / HK

highlight: a11y-light

我正在參加「初夏創意投稿大賽」詳情請看:初夏創意投稿大賽

摘要

當520來臨而又恰逢夏日時節,暗戀女神多日的我決定今晚把她約出去看電影,一想到晚上的甜蜜時光,我直接拿起手機直接給女神發訊息,掘友們等著吃狗糧吧,管飽。

image.png

既然女神這麼熱,我身為一個react使者怎能甘心呢,不行,要給女神寫個空調,說不定今晚可以出去二人世界了呢。要寫個夏日空調可真難,倒不是程式碼繁瑣,只是這夏日空調得蘸上四分春風,三分月色,兩分微醺,還有一分她的眉眼才好。咱們話不多說,上號。

image.png

建立專案

```js

create-react-app air-condition ```

元件劃分

無論寫react還是vue,最重要的一點永遠都有如何確定元件分工,元件就是分而治之的思想,它就像一個備胎,哪裡需要哪裡搬!因為這個React夏日空調並不算複雜,甚至有點簡單,所以html、css部分不多贅述,我們直接來看怎麼劃分元件。如下圖所示,App元件為根元件,包裹Panel、CopyWriting、Button、Audio元件,Panel元件中又包含Temp元件,我們將狀態(空調當前溫度)、行為(調節按鈕、聲音的播放與暫停)統統放在App元件中,因為Panel、Button、Audio元件之間需要共享資料,而最方便的方法無疑是將共有資料存放在其共同的父元件中,這種方式React稱之為狀態提升

image.png

元件分工

index

```js import React from 'react' import ReactDOM from 'react-dom/client'

import App from './App'

const root = ReactDOM.createRoot(document.querySelector('#root')) root.render( ) ```

App

```js import { useState, useRef, useCallback } from 'react'

import Panel from './component/Panel' import CopyWriting from './component/CopyWriting' import Button from './component/Button' import Audio from './component/Audio'

const App = () => { // 使用useRef得到Audio元件的例項 const ref = useRef() // temp為儲存的空調溫度,預設為26 const [temp, setTemp] = useState(26) // 調高溫度 const up = useCallback(() => { const result = temp + 1 ref.current.playTip() // 溫度最高為31 if (result > 31) { return false } setTemp(result) }, [temp]) // 調低溫度 const down = useCallback(() => { const result = temp - 1 ref.current.playTip() // 溫度最低為16 if (result < 16) { return false } setTemp(result) }, [temp]) // 開關按鈕 const toSwitch = useCallback(() => { ref.current.playTip() ref.current.playRun() }, [temp])

return ( <>

夏日空調


) }

export default App ``` App元件中我們使用useRef繼而得到子元件Audio中的方法。up、down、toSwitch方法通過props傳遞給Button元件,為了引起Button元件的不必要渲染,我們使用useCallback包裹住傳遞的方法,同時Button元件自身使用React.memo進行包裹。

Panel

```js import Temp from '../Temp'

// 生成div const productDiv = length => Array.from({ length }).map((_, i) =>

)

const Panel = ({ temp }) => { return (

{productDiv(6)}

{productDiv(9)}
{productDiv(6)}
{productDiv(6)}
{productDiv(6)}
failed failed failed
) }

export default Panel ``` 我們也可以選擇不使用Temp元件,而是將溫度直接顯示在Panel元件中,這種方式同樣可以實現效果,但是為了資料和頁面更好的分離,我們的Panel元件只負責接收資料,Temp元件只負責顯示資料,這樣如果後期需要對Panel元件新增一些複雜互動、功能,我們只需要對Panel元件進行修改,如果需要對空調溫度的顯示介面進行修改時,我們只需要修改Temp元件即可。

Temp

```js import React from 'react'

const Temp = ({ temp }) => { return ( <> {temp} c
) }

export default React.memo(Temp) ``` Temp元件只是用來展示UI,不進行其它任何操作,無論Temp元件後期修改後多麼花裡胡哨,溫度(temp)我們已經拿到了,無論怎麼修改均不影響其它功能。同樣,為了引起Temp元件的不必要渲染,我們依然使用React.memo對元件自身進行包裹。

CopyWriting

```js import { useState, useEffect } from "react"

// 獲取文案 const getContent = async () => { try { const connect = await fetch('https://v1.hitokoto.cn?c=d') const data = await connect.json() return data } catch (e) { return false } }

const CopyWriting = () => { // 儲存獲取到的文案 const [text, setText] = useState({ hitokoto: '', quote: '' }) useEffect(() => { const update = async () => { const data = await getContent() const from_who = data.from_who ? data.from_who : '' data.quote = ——${from_who}《${data.from}》 setText(data) } const token = setInterval(update, 6000) update() // 元件解除安裝時清除定時器 return () => clearInterval(token) }, []) return ( <>

{text.hitokoto}

{text.quote}


) }

export default CopyWriting 因為本專案相對較小,無需其它複雜功能,所以getContent採用fetch獲取文案,如果非要使用xhr、axios倒顯得有些冗餘了。我們此處將獲取到的文案存放在了useState中,還有一種方式,此處並未採用。即使用useRef來儲存資料,當獲取到資料後,使用useState重新整理頁面。例如js const Comp = () => { const ref = useRef(1) const [force, setForce] = useState() const handle = () => { const result = ref.current ref.current = result + 1 setForce(result) } return ( <> {ref.current}
) } ```

Button

```js import React, { useState } from 'react'

const Button = ({ up, down, toSwitch }) => { // 儲存開關按鈕的背景顏色 const [bg, setBg] = useState({ open: '#f33531', off: '#43a047', flag: false }) // 播放聲音、切換狀態 const switchState = () => { toSwitch() setBg({ ...bg, flag: !bg.flag }) } return ( <>

up()}> failed
switchState()} style={{ backgroundColor: bg.flag ? bg.open : bg.off }} > failed
down()}> failed

) }

export default React.memo(Button) ``` Button元件自身使用React.memo進行包裹,一般情況下,React.memo與useCallback、useMemo搭配使用,使用useCallback、useMemo的原因是無論父元件如何操作,始終保證傳遞給Button元件的props不變,使用React.memo是為了對props做比較,這樣才不會引起Button元件的重新渲染。React.memo類似於類式元件的PureComponent。

Audio

```js import React, { useRef, useImperativeHandle } from 'react'

const Audio = (_, ref) => { const tipRef = useRef() const runRef1 = useRef() // 要傳遞給父元件的方法 useImperativeHandle(ref, () => ({ playTip: () => { tipRef.current.play() }, playRun: () => { const flag = runRef1.current.paused if (flag) return setTimeout(() => runRef1.current.play(), 300); runRef1.current.pause() }, })) return ( <>
) }

// 使用React.forwardRef將子元件ref轉發給父元件 export default React.forwardRef(Audio) Audio元件中,我們使用React.forwardRef與useImperativeHandle讓父元件可以操作子元件。注意,類式元件並不需要這麼做,只需要在使用子元件時直接ref即可,顯然,hooks並不支援這種寫法。js // son class Son extends React.Component { constructor() { super() this.state = { value: 1 } this.inc = () => this.setState({ value: this.state.value + 1 }) } render() { const { value } = this.state const { inc } = this return ( <> {value}
) } } // father // father可以是函式式元件也可以是類式元件 class Father extends React.Component { constructor() { super() this.ref = React.createRef() this.inc = () => this.ref.current.inc() } render() { const { ref, inc } = this return ( <>
) } } ```

專案完成

```js

npm start ``` image.png

線上演示地址

1.線上演示

2.碼上掘金 碼上掘金

專案到這裡就已經大功告成了,另外給大家推薦一下我自己寫的JS每日一題JavsScript每日一題專欄每天用2-3分鐘做一做,我相信技術肯定會得到提升的。兄弟們回見了,我要去拿給女神看看了。

image.png

我心想,女神一定高興壞了吧,今晚應該去看什麼電影呢,聽說520檔期有《可不可以不要離開我》、《暗戀:橘生淮南》,我相信有她陪我,每一天都是不重複的電影。

image.png

女神為什麼這樣啊,難道我的空調不夠好嗎?思來想去,可能兔兔去睡覺了不希望被人打擾罷了。