520女神说她很热,我用React写了个夏日空调给她

共 7263字,需浏览 15分钟

 ·

2022-05-25 13:09


点击上方 前端阳光,关注公众号

回复加群,加入技术交流群交流群

摘要

当520来临而又恰逢夏日时节,暗恋女神多日的我决定今晚把她约出去看电影,一想到晚上的甜蜜时光,我直接拿起手机直接给女神发消息,掘友们等着吃狗粮吧,管饱。

既然女神这么热,我身为一个react使者怎能甘心呢,不行,要给女神写个空调,说不定今晚可以出去二人世界了呢。要写个夏日空调可真难,倒不是代码繁琐,只是这夏日空调得蘸上四分春风,三分月色,两分微醺,还有一分她的眉眼才好。咱们话不多说,上号。

创建项目

> create-react-app air-condition

组件划分

无论写react还是vue,最重要的一点永远都有如何确定组件分工,组件就是分而治之的思想,它就像一个备胎,哪里需要哪里搬!因为这个React夏日空调并不算复杂,甚至有点简单,所以html、css部分不多赘述,我们直接来看怎么划分组件。如下图所示,App组件为根组件,包裹Panel、CopyWriting、Button、Audio组件,Panel组件中又包含Temp组件,我们将状态(空调当前温度)、行为(调节按钮、声音的播放与暂停)统统放在App组件中,因为Panel、Button、Audio组件之间需要共享数据,而最方便的方法无疑是将共有数据存放在其共同的父组件中,这种方式React称之为状态提升。

组件分工

index

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

import App from './App'

const root = ReactDOM.createRoot(document.querySelector('#root'))
root.render(
  <React.StrictMode>
    <App />
  React.StrictMode>

)

App

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 (
    <>
      <section className="title">
        <h1 style={{ fontWeight: 400 }}>夏日空调h1>

      section>
      <section className="panel">
        <Panel temp={temp} />
      section>
      <section className="copywriting">
        <CopyWriting />
      section>
      <section className="button">
        <Button up={up} down={down} toSwitch={toSwitch} />
      section>
      <section style={{ display: 'none' }}>
        <Audio ref={ref} />
      section>
    
  )
}

export default App

App组件中我们使用useRef继而得到子组件Audio中的方法。up、down、toSwitch方法通过props传递给Button组件,为了引起Button组件的不必要渲染,我们使用useCallback包裹住传递的方法,同时Button组件自身使用React.memo进行包裹。

### Panel
import Temp from '../Temp'

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

const Panel = ({ temp }) => {
    return (
        <div className="air-condition">
            <div className="a-tag">
                <div className="a-t-header">
                    {productDiv(6)}
                div>

                <div className="a-t-content-1">
                    <div className="c-l">
                        <div className="c-l-1">div>
                        <div className="c-l-2">div>
                        <div className="c-l-3">div>
                    div>
                    <div className="c-r">
                        <p className="c-r-grade">p>
                    div>
                div>
                <div className="a-t-content-2">
                    <div className="c-2-title">
                        {productDiv(9)}
                    div>
                    <div className="c-2">
                        <div className="c-2-1">
                            {productDiv(6)}
                        div>
                        <div className="c-2-2">
                            {productDiv(6)}
                        div>
                    div>
                div>
                <div className="a-t-footer">
                    {productDiv(6)}
                div>
            div>
            <div className="a-screen">
                <div className="s-icon">
                    <img src={snowflake} className="img-100" alt="" />
                div>
                <div className="s-temp">
                    <Temp temp={temp} />
                div>
            div>
            <div className="a-line">div>
            <div className="a-wind">
                <img src={wind} alt="failed" className="img-l" />
                <img src={wind} alt="failed" className="img-c" />
                <img src={wind} alt="failed" className="img-r" />
            div>
        div>
    )
}

export default Panel

我们也可以选择不使用Temp组件,而是将温度直接显示在Panel组件中,这种方式同样可以实现效果,但是为了数据和页面更好的分离,我们的Panel组件只负责接收数据,Temp组件只负责显示数据,这样如果后期需要对Panel组件添加一些复杂交互、功能,我们只需要对Panel组件进行修改,如果需要对空调温度的显示界面进行修改时,我们只需要修改Temp组件即可。

Temp

import React from 'react'

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

            <span className="s-t-symbol">span>
            <span>cspan>
        
    )
}

export default React.memo(Temp)

Temp组件只是用来展示UI,不进行其它任何操作,无论Temp组件后期修改后多么花里胡哨,温度(temp)我们已经拿到了,无论怎么修改均不影响其它功能。同样,为了引起Temp组件的不必要渲染,我们依然使用React.memo对组件自身进行包裹。

CopyWriting

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 (
        <>
            <p>{text.hitokoto}p>

            <p>{text.quote}p>
        
    )
}

export default CopyWriting

因为本项目相对较小,无需其它复杂功能,所以getContent采用fetch获取文案,如果非要使用xhr、axios倒显得有些冗余了。我们此处将获取到的文案存放在了useState中,还有一种方式,此处并未采用。即使用useRef来存储数据,当获取到数据后,使用useState刷新页面。例如

const Comp = () => {
    const ref = useRef(1)
    const [force, setForce] = useState()
    const handle = () => {
        const result = ref.current
        ref.current = result + 1
        setForce(result)
    }
    return (
        <>
            <span>{ref.current}span>

            <button onClick={handle}>自增button>
        
    )
}

Button

import React, { useState } from 'react'

const Button = ({ up, down, toSwitch }) => {
    // 存储开关按钮的背景颜色
    const [bg, setBg] = useState({ open'#f33531'off'#43a047'flagfalse })
    // 播放声音、切换状态
    const switchState = () => {
        toSwitch()
        setBg({ ...bg, flag: !bg.flag })
    }
    return (
        <>
            <div className="b-inc" onClick={() => up()}>
                <img src={upArrow} className="img-5" alt="failed" />
            div>

            <div
                className="b-switch"
                onClick={() =>
 switchState()}
                style={{ backgroundColor: bg.flag ? bg.open : bg.off }}
            >
                <img src={switchBtns} className="img-5" alt="failed" />
            div>
            <div className="b-dec" onClick={() => down()}>
                <img src={downArrow} className="img-5" alt="failed" />
            div>
        
    )
}

export default React.memo(Button)

Button组件自身使用React.memo进行包裹,一般情况下,React.memo与useCallback、useMemo搭配使用,使用useCallback、useMemo的原因是无论父组件如何操作,始终保证传递给Button组件的props不变,使用React.memo是为了对props做比较,这样才不会引起Button组件的重新渲染。React.memo类似于类式组件的PureComponent。

Audio

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 (
        <>
            <audio src={tip} ref={tipRef} >audio>

            <audio src={run} ref={runRef1} loop>audio>
        
    )
}

// 使用React.forwardRef将子组件ref转发给父组件
export default React.forwardRef(Audio)

Audio组件中,我们使用React.forwardRef与useImperativeHandle让父组件可以操作子组件。注意,类式组件并不需要这么做,只需要在使用子组件时直接ref即可,显然,hooks并不支持这种写法。

// son
class Son extends React.Component {
  constructor() {
    super()
    this.state = { value1 }
    this.inc = () => this.setState({ valuethis.state.value + 1 })
  }
  render() {
    const { value } = this.state
    const { inc } = this
    return (
      <>
        <span>{value}span>

        <button onClick={inc}>加1button>
      
    )
  }
}
// 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 (
      <>
        <Son ref={ref} />
        <button onClick={inc}>加21button>

      
    )
  }
}

项目完成

> npm start

在线演示地址 1.在线演示https://link.juejin.cn/?target=http%3A%2F%2Fzhangbenjin.cn%2F

项目到这里就已经大功告成了,另外给大家推荐一下我自己写的原创文章https://github.com/Sunny-lucking/blog,我相信技术肯定会得到提升的。兄弟们回见了,我要去拿给女神看看了。

我心想,女神一定高兴坏了吧,今晚应该去看什么电影呢,听说520档期有《可不可以不要离开我》、《暗恋:橘生淮南》,我相信有她陪我,每一天都是不重复的电影。

女神为什么这样啊,难道我的空调不够好吗?思来想去,可能兔兔去睡觉了不希望被人打扰罢了。

作者:FuncJin 链接:https://juejin.cn/post/7100478900673150989


往期推荐


优秀文章汇总:https://github.com/Sunny-lucking/blog

内推:https://www.yuque.com/peigehang/kb

技术交流群


我组建了技术交流群,里面有很多 大佬,欢迎进来交流、学习、共建。回复 加群 即可。后台回复「电子书」即可免费获取 27本 精选的前端电子书!回复内推,可内推各厂内推码



   “分享、点赞在看” 支持一波👍


浏览 41
点赞
评论
收藏
分享

手机扫一扫分享

分享
举报
评论
图片
表情
推荐
点赞
评论
收藏
分享

手机扫一扫分享

分享
举报