欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

利用React實現(xiàn)一個有點(diǎn)意思的電梯小程序

 更新時間:2022年08月18日 10:29:29   作者:夕水  
這篇文章主要為大家詳解介紹了如何利用React實現(xiàn)一個有點(diǎn)意思的電梯小程序,文中的示例代碼講解詳細(xì),感興趣的小伙伴可以跟隨小編一起了解一下

查看效果

我們先來看一下今天要實現(xiàn)的示例的效果,如下所示

好,接下來我們也看到了這個示例的效果,讓我們進(jìn)入正題,開始愉快的編碼吧。

技術(shù)棧介紹

這個小程序,我們將采用React + typescript + css in js語法編寫,并且采用最新比較流行的工具vite來構(gòu)建。

初始化項目

我們可以選擇在電腦按住shift,然后右鍵,選擇powershell,也就是默認(rèn)的系統(tǒng)終端。然后輸入命令:

mkdir react-elevator

創(chuàng)建一個目錄,創(chuàng)建好之后,接著我們在vscode中打開這個目錄,打開之后,在vscode中打開終端,輸入以下命令:

npm init vite@latest react-elevator -- --template react-ts

注意在命令界面,我們要選擇react,react-ts。初始化項目好了之后,我們在輸入命令:

cd react-elevator
npm install
npm run dev

查看一下我們初始化項目是否成功。

特別聲明: 請注意安裝了node.js和npm工具

css in js

可以看到,我們的項目初始化已經(jīng)完成,好,接下來,我們還要額外的裝一些項目當(dāng)中遇到的依賴,例如css in js,我們需要安裝@emotion/styled,@emotion/react依賴。繼續(xù)輸入命令:

npm install @emotion/styled @emotion/react --save-dev

安裝好之后,我們在項目里面使用一下該語法。

首先引入styled,如下:

import styled from "@emotion/styled"

接著創(chuàng)建一個樣式組件,css in js實際上就是把每個組件當(dāng)成一個樣式組件,我們可以通過styled后面跟html標(biāo)簽名,然后再跟模板字符串,結(jié)構(gòu)如下:

const <組件名> = styled.<html標(biāo)簽名>`
    //這里寫樣式代碼
`

例如:

const Link = styled.a`
    color:#fff;
`

以上代碼就是寫一個字體顏色為白色的超鏈接組件,然后我們就可以在jsx當(dāng)中直接寫link組件。如下所示:

<div>
    <Link>這是一個超鏈接組件</Link>
</div>

當(dāng)然emotion還支持對象寫法,但是我們這里基本上只用模板字符串語法就夠了。

接下來步入正題,我們首先刪除初始化的一些代碼,因為我們沒有必要用到。

分析程序的結(jié)構(gòu)

好刪除之后,我們接下來看一下我們要實現(xiàn)的電梯小程序的結(jié)構(gòu):

1.電梯井(也就是電梯上升或者下降的地方)

2.電梯

3.電梯門(分為左右門)

4.樓層

  • 4.1 樓層數(shù)
  • 4.2 樓層按鈕(包含上升和下降按鈕)

結(jié)構(gòu)好了之后,接下來我們來看看有哪些功能:

  • 點(diǎn)擊樓層,催動電梯上升或者下降
  • 電梯到達(dá)對應(yīng)樓層,電梯左右門打開
  • 門打開之后,里面的美女就出來啦
  • 按鈕會有一個點(diǎn)擊選中的效果

我們先來分析結(jié)構(gòu),根據(jù)以上的拆分,我們可以大致將整個小程序分成如下幾個組件:

1.樓房(容器組件)

2.電梯井組件

2.1 電梯組件

2.1.1 電梯左邊的門

2.1.1 電梯右邊的門

3.樓層組件

3.1 樓層控制組件

3.1.1 樓層上升按鈕組件

3.1.2 樓層下降按鈕組件

3.2 樓層數(shù)組件

我們先來寫好組件和樣式,然后再完成功能。

樓房組件

首先是我們的樓房組件,我們新建一個components目錄,再新建一個ElevatorBuild.tsx組件,里面寫上如下代碼:

import styled from "@emotion/styled"

const StyleBuild = styled.div`
    width: 350px;
    max-width: 100%;
    min-height: 500px;
    border: 6px solid var(--elevatorBorderColor--);
    overflow: hidden;
    display: flex;
    margin: 3vh auto;
`

const ElevatorBuild = () => {
    return (
        <StyleBuild></StyleBuild>
    )
}

export default ElevatorBuild

這樣,我們的一個樓房組件就算是完成了,然后我們在App.tsx當(dāng)中引入,并使用它:

//這里是新增的代碼
import ElevatorBuild from "./components/ElevatorBuild"

const App = () => (
  <div className="App">
    {/*這里是新增的代碼 */}
    <ElevatorBuild />
  </div>
)

export default App

全局樣式

在這里,我們定義了全局css變量樣式,因此在當(dāng)前目錄下創(chuàng)建global.css,并在main.tsx中引入,然后在該樣式文件中寫上如下代碼:

:root {
    --elevatorBorderColor--: rgba(0,0,0.85);
    --elevatorBtnBgColor--: #fff;
    --elevatorBtnBgDisabledColor--: #898989;
    --elevatorBtnDisabledColor--: #c2c3c4;
}

* {
    margin: 0;
    padding: 0;
    box-sizing: border-box;
}

電梯井組件

接下來,讓我們繼續(xù)完成電梯井組件,同樣在components目錄下新建一個ElevatorShaft.tsx組件,里面寫上如下代碼:

import styled from "@emotion/styled"

const StyleShaft = styled.div`
    width: 200px;
    position: relative;
    border-right: 2px solid var(--elevatorBorderColor--);
    padding: 1px;
`

const ElevatorShaft = () => {
    return (
        <StyleShaft></StyleShaft>
    )
}

export default ElevatorShaft

然后我們在樓房組件中引入并使用它,如下所示:

import styled from "@emotion/styled"
//這里是新增的代碼
import ElevatorShaft from "./ElevatorShaft"

const StyleBuild = styled.div`
    width: 350px;
    max-width: 100%;
    min-height: 500px;
    border: 6px solid var(--elevatorBorderColor--);
    overflow: hidden;
    display: flex;
    margin: 3vh auto;
`

const ElevatorBuild = () => {
    return (
        <StyleBuild>
            {/*這里是新增的代碼 */}
            <ElevatorShaft></ElevatorShaft>
        </StyleBuild>
    )
}

export default ElevatorBuild

電梯門組件

接著我們來完成電梯門組件,我們可以看到電梯門組件有一些公共的樣式部分,所以我們可以抽取出來,新建一個Door.tsx,寫上如下代碼:

import styled from '@emotion/styled';

const StyleDoor = styled.div`
    width:50%;
    position: absolute;
    top: 0;
    height: 100%;
    background-color: var(--elevatorBorderColor--);
    border: 1px solid var(--elevatorBtnBgColor--);
`;

const StyleLeftDoor = styled(StyleDoor)`
    left: 0;
`;

const StyleRightDoor = styled(StyleDoor)`
    right: 0;
`;


export { StyleLeftDoor,StyleRightDoor }

由于我們功能會需要設(shè)置這兩個組件的樣式,并且我們這個樣式是設(shè)置在style屬性上的,因此我們可以通過props來傳遞,現(xiàn)在我們先寫好typescript接口類,創(chuàng)建一個type目錄,新建style.d.ts全局接口文件,并寫上如下代碼:

export interface StyleProps {
    style: CSSProperties
}

電梯組件

接下來,我們就可以開始寫電梯組件,如下所示:

import styled from "@emotion/styled"

const StyleElevator = styled.div`
    height: 98px;
    background: url("https://www.eveningwater.com/my-web-projects/js/26/img/6.jpg") center / cover no-repeat;
    border: 1px solid var(--elevatorBorderColor--);
    width: calc(100% - 2px);
    padding: 1px;
    transition-timing-function: ease-in-out;
    position: absolute;
    left: 1px;
    bottom: 1px;
`

const Elevator = (props: Partial<ElevatorProps>) => {
    return (
        <StyleElevator>
     
        </StyleElevator>
    )
}

export default Elevator

接下來,我們來看兩個電梯門組件,首先是左邊的門,如下所示:

import { StyleProps } from "../type/style"
import { StyleLeftDoor } from "./Door"

const ElevatorLeftDoor = (props: Partial<StyleProps>) => {
    const { style } = props
    return (
        <StyleLeftDoor style={style}></StyleLeftDoor>
    )
}

export default ElevatorLeftDoor

Partial是一個泛型,傳入接口,代表將接口的每個屬性變成可選屬性,根據(jù)這個原理,我們可以得知右邊門的組件代碼也很類似。如下:

import { StyleProps } from '../type/style';
import { StyleRightDoor } from './Door'
const ElevatorRightDoor = (props: Partial<StyleProps>) => {
    const { style } = props;
    return (
        <StyleRightDoor style={style}/>
    )
}

export default ElevatorRightDoor;

這兩個組件寫好之后,我們接下來要在電梯組件里引入并使用它們,由于功能邏輯會需要設(shè)置樣式,因此,我們通過props再次傳遞style。如下所示:

import styled from "@emotion/styled"
import { StyleProps } from "../type/style";
import ElevatorLeftDoor from "./ElevatorLeftDoor"
import ElevatorRightDoor from "./ElevatorRightDoor"

const StyleElevator = styled.div`
    height: 98px;
    background: url("https://www.eveningwater.com/my-web-projects/js/26/img/6.jpg") center / cover no-repeat;
    border: 1px solid var(--elevatorBorderColor--);
    width: calc(100% - 2px);
    padding: 1px;
    transition-timing-function: ease-in-out;
    position: absolute;
    left: 1px;
    bottom: 1px;
`

export interface ElevatorProps {
    leftDoorStyle: StyleProps['style'];
    rightDoorStyle: StyleProps['style'];
}

const Elevator = (props: Partial<ElevatorProps>) => {
    const { leftDoorStyle,rightDoorStyle } =  props;
    return (
        <StyleElevator>
            <ElevatorLeftDoor style={leftDoorStyle} />
            <ElevatorRightDoor style={rightDoorStyle} />
        </StyleElevator>
    )
}

export default Elevator

完成了電梯組件之后,接下來我們在電梯井組件里面引入電梯組件,注意這里后續(xù)邏輯我們會設(shè)置電梯組件和電梯門組件的樣式,因此在電梯井組件中,我們需要通過props傳遞樣式。

import styled from "@emotion/styled"
import { StyleProps } from "../type/style";
import Elevator from "./Elevator"


const StyleShaft = styled.div`
    width: 200px;
    position: relative;
    border-right: 2px solid var(--elevatorBorderColor--);
    padding: 1px;
`

export interface ElevatorProps {
    leftDoorStyle: StyleProps['style'];
    rightDoorStyle: StyleProps['style'];
    elevatorStyle: StyleProps['style'];
}

const ElevatorShaft = (props: Partial<ElevatorProps>) => {
    const { leftDoorStyle,rightDoorStyle,elevatorStyle } = props;
    return (
        <StyleShaft>
            <Elevator style={elevatorStyle} leftDoorStyle={leftDoorStyle} rightDoorStyle={rightDoorStyle}></Elevator>
        </StyleShaft>
    )
}

export default ElevatorShaft

電梯門組件的開啟動畫

我們可以看到,當(dāng)?shù)竭_(dá)一定時間,電梯門會有開啟動畫,這里我們顯然沒有加上,所以我們可以為電梯門各自加一個是否開啟的props用來傳遞,繼續(xù)修改Door.tsx如下:

import styled from '@emotion/styled';

const StyleDoor = styled.div`
    width:50%;
    position: absolute;
    top: 0;
    height: 100%;
    background-color: var(--elevatorBorderColor--);
    border: 1px solid var(--elevatorBtnBgColor--);
`;

const StyleLeftDoor = styled(StyleDoor)<{ toggle?:boolean }>`
    left: 0;
    ${({toggle}) => toggle ? 'animation: doorLeft 3s 1s cubic-bezier(0.075, 0.82, 0.165, 1);' : '' }
    @keyframes doorLeft {
        0% {
            left: 0px;
        }
        25% {
            left: -90px;
        }
        50% {
            left: -90px;
        }
        100% {
            left:0;
        }
    }
`;

const StyleRightDoor = styled(StyleDoor)<{ toggle?:boolean }>`
    right: 0;
    ${({toggle}) => toggle ? 'animation: doorRight 3s 1s cubic-bezier(0.075, 0.82, 0.165, 1);' : '' };
    @keyframes doorRight {
        0% {
            right: 0px;
        }
        25% {
            right: -90px;
        }
        50% {
            right: -90px;
        }
        100% {
            right:0;
        }
    }
`;


export { StyleLeftDoor,StyleRightDoor }

emotion語法可以通過函數(shù)來返回一個css屬性,從而達(dá)到動態(tài)設(shè)置屬性的目的,一對尖括號,其實也就是typescript中的泛型,代表是否傳入toggle數(shù)據(jù),接下來修改ElevatorLeftDoor.tsx和ElevatorRightDoor.tsx。如下:

import { StyleProps } from "../type/style";
import { StyleLeftDoor } from "./Door"

export interface ElevatorLeftDoorProps extends StyleProps {
    toggle: boolean
}

const ElevatorLeftDoor = (props: Partial<ElevatorLeftDoorProps>) => {
    const { style,toggle } = props;
    return (
        <StyleLeftDoor style={style} toggle={toggle}></StyleLeftDoor>
    )
}

export default ElevatorLeftDoor
import { StyleProps } from '../type/style'
import { StyleRightDoor } from './Door'

export interface ElevatorRightDoorProps extends StyleProps {
    toggle: boolean
}

const ElevatorRightDoor = (props: Partial<ElevatorRightDoorProps>) => {
    const { style,toggle } = props;
    return (
        <StyleRightDoor style={style} toggle={toggle} />
    )
}

export default ElevatorRightDoor

修改電梯和電梯井組件

同樣的我們也需要修改電梯組件和電梯井組件,如下所示:

import styled from "@emotion/styled"
import { StyleProps } from "../type/style";
import ElevatorLeftDoor from "./ElevatorLeftDoor"
import ElevatorRightDoor from "./ElevatorRightDoor"

const StyleElevator = styled.div`
    height: 98px;
    background: url("https://www.eveningwater.com/my-web-projects/js/26/img/6.jpg") center / cover no-repeat;
    border: 1px solid var(--elevatorBorderColor--);
    width: calc(100% - 2px);
    padding: 1px;
    transition-timing-function: ease-in-out;
    position: absolute;
    left: 1px;
    bottom: 1px;
`

export interface ElevatorProps extends StyleProps {
    leftDoorStyle: StyleProps['style']
    rightDoorStyle: StyleProps['style']
    leftToggle: boolean
    rightToggle: boolean
}

const Elevator = (props: Partial<ElevatorProps>) => {
    const { leftDoorStyle,rightDoorStyle,leftToggle,rightToggle } =  props;
    return (
        <StyleElevator>
            <ElevatorLeftDoor style={leftDoorStyle} toggle={leftToggle} />
            <ElevatorRightDoor style={rightDoorStyle} toggle={rightToggle}/>
        </StyleElevator>
    )
}

export default Elevator
import styled from "@emotion/styled";
import { StyleProps } from "../type/style";
import Elevator from "./Elevator";

const StyleShaft = styled.div`
  width: 200px;
  position: relative;
  border-right: 2px solid var(--elevatorBorderColor--);
  padding: 1px;
`;

export interface ElevatorProps {
  leftDoorStyle: StyleProps["style"];
  rightDoorStyle: StyleProps["style"];
  elevatorStyle: StyleProps["style"];
  leftToggle: boolean;
  rightToggle: boolean;
}

const ElevatorShaft = (props: Partial<ElevatorProps>) => {
  const {
    leftDoorStyle,
    rightDoorStyle,
    elevatorStyle,
    leftToggle,
    rightToggle,
  } = props;
  return (
    <StyleShaft>
      <Elevator
        style={elevatorStyle}
        leftDoorStyle={leftDoorStyle}
        rightDoorStyle={rightDoorStyle}
        leftToggle={leftToggle}
        rightToggle={rightToggle}
      ></Elevator>
    </StyleShaft>
  );
};

export default ElevatorShaft;

但是別忘了我們這里的電梯組件因為需要上升和下降,因此還需要設(shè)置樣式,再次修改一下電梯組件的代碼如下:

import styled from "@emotion/styled"
import { StyleProps } from "../type/style";
import ElevatorLeftDoor from "./ElevatorLeftDoor"
import ElevatorRightDoor from "./ElevatorRightDoor"

const StyleElevator = styled.div`
    height: 98px;
    background: url("https://www.eveningwater.com/my-web-projects/js/26/img/6.jpg") center / cover no-repeat;
    border: 1px solid var(--elevatorBorderColor--);
    width: calc(100% - 2px);
    padding: 1px;
    transition-timing-function: ease-in-out;
    position: absolute;
    left: 1px;
    bottom: 1px;
`

export interface ElevatorProps extends StyleProps {
    leftDoorStyle: StyleProps['style']
    rightDoorStyle: StyleProps['style']
    leftToggle: boolean
    rightToggle: boolean
}

const Elevator = (props: Partial<ElevatorProps>) => {
    const { style,leftDoorStyle,rightDoorStyle,leftToggle,rightToggle } =  props;
    return (
        <StyleElevator style={style}>
            <ElevatorLeftDoor style={leftDoorStyle} toggle={leftToggle} />
            <ElevatorRightDoor style={rightDoorStyle} toggle={rightToggle}/>
        </StyleElevator>
    )
}

export default Elevator

樓層容器組件

到目前為止,我們的左半邊部分已經(jīng)完成了,接下來,我們來完成右半邊部分的樓層數(shù)和控制按鈕組件,我們的樓層是動態(tài)生成的,因此我們需要一個容器組件包裹起來,先寫這個樓層容器組件,如下所示:

import styled from "@emotion/styled"

const StyleStoreyZone = styled.div`
    width: auto;
    height: 100%;
`

const Storey = () => {
    return (
        <StyleStoreyZone>
            
        </StyleStoreyZone>
    )
}

export default Storey

樓層組件

可以看到樓層容器組件還是比較簡單的,接下來我們來看樓層組件。如下所示:

import styled from "@emotion/styled";
import { createRef, useEffect, useState } from "react";
import useComponentDidMount from "../hooks/useComponentDidMount";

const StyleStorey = styled.div`
  display: flex;
  align-items: center;
  height: 98px;
  border-bottom: 1px solid var(--elevatorBorderColor--);
`;

const StyleStoreyController = styled.div`
  width: 70px;
  height: 98px;
  padding: 8px 0;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  flex-direction: column;
`;

const StyleStoreyCount = styled.div`
  width: 80px;
  height: 98px;
  text-align: center;
  font: 56px / 98px 微軟雅黑, 楷體;
`;

const StyleButton = styled.button`
  width: 36px;
  height: 36px;
  border: 1px solid var(--elevatorBorderColor--);
  border-radius: 50%;
  outline: none;
  cursor: pointer;
  background-color: var(--elevatorBtnBgColor--);
  &:last-of-type {
    margin-top: 8px;
  }
  &.checked {
    background-color: var(--elevatorBorderColor--);
    color: var(--elevatorBtnBgColor--);
  }
  &[disabled] {
    cursor: not-allowed;
    background-color: var(--elevatorBtnBgDisabledColor--);
    color: var(--elevatorBtnDisabledColor--);
  }
`;

export interface MethodProps {
  onUp(v: number, t: number, h?: number): void;
  onDown(v: number, t: number, h?: number): void;
}

export interface StoreyProps extends MethodProps{
  count: number
}

export interface StoreyItem {
   key: string
   disabled: boolean
}

const Storey = (props: Partial<StoreyProps>) => {
  const { count = 6 } = props;
  const storeyRef = createRef<HTMLDivElement>();
  const [storeyList, setStoreyList] = useState<StoreyItem []>();
  const [checked, setChecked] = useState<string>();
  const [type, setType] = useState<keyof MethodProps>();
  const [offset,setOffset] = useState(0)
  const [currentFloor, setCurrentFloor] = useState(1);
  useComponentDidMount(() => {
    let res: StoreyItem [] = [];
    for (let i = count - 1; i >= 0; i--) {
      res.push({
        key: String(i + 1),
        disabled: false
      });
    }
    setStoreyList(res);
  });

  useEffect(() => {
    if(storeyRef){
      setOffset(storeyRef.current?.offsetHeight as number)
    }
  },[storeyRef])

  const onClickHandler = (key: string,index:number,method: keyof MethodProps) => {
    setChecked(key)
    setType(method)
    const moveFloor = count - index
    const diffFloor = Math.abs(moveFloor - currentFloor)
    setCurrentFloor(moveFloor)    
    props[method]?.(diffFloor, offset * (moveFloor - 1))
    // 也許這不是一個好的方法
    if(+key !== storeyList?.length && +key !== 1){
        setStoreyList((storey) => storey?.map(item => ({ ...item,disabled: true })))
    }
    setTimeout(() => {
      setChecked(void 0);
      if(+key !== storeyList?.length && +key !== 1){
        setStoreyList((storey) => storey?.map(item => ({ ...item,disabled: false })))
      }
    }, diffFloor * 1000);
  };
  return (
    <>
      {storeyList?.map((item,index) => (
        <StyleStorey key={item.key} ref={storeyRef}>
          <StyleStoreyController>
            <StyleButton
              disabled={Number(item.key) === storeyList.length || item.disabled}
              onClick={() => onClickHandler(item.key,index,'onUp')}
              className={`${item.key === checked && type === 'onUp' ? "checked" : ""}`}
            >
              ↑
            </StyleButton>
            <StyleButton
              disabled={Number(item.key) === 1 || item.disabled}
              onClick={() => onClickHandler(item.key,index,'onDown')}
              className={`${item.key === checked && type === 'onDown' ? "checked" : ""}`}
            >
              ↓
            </StyleButton>
          </StyleStoreyController>
          <StyleStoreyCount>{item.key}</StyleStoreyCount>
        </StyleStorey>
      ))}
    </>
  );
};

export default Storey;

可以看到樓層組件的邏輯非常多,但其實一項一項的分析下來也并不難。

接下來,我們在該容器組件中引入,并且將該組件在樓房組件中引入,就可以得到我們整個電梯小程序的結(jié)構(gòu)了。

在這里我們來一步一步的分析樓層組件的邏輯,

樓層數(shù)

首先樓層是動態(tài)生成的,通過父組件傳遞,因此我們在props當(dāng)中定義一個count,默認(rèn)值是6,代表默認(rèn)生成的樓層數(shù)。這也就是我們這行代碼的意義:

export interface StoreyProps extends MethodProps{
  count: number
}
const { count = 6 } = props;

樓層的上升與下降

其次我們在對電梯進(jìn)行上升和下降的時候,需要獲取到每一層樓高,實際上也就是樓層容器元素的高度,如何獲取DOM元素的實際高度?我們先想一下,如果是一個真實的DOM元素,我們只需要獲取offsetHeight就行了,即:

//這里的el顯然是一個dom元素
const offset: number = el.offsetHeight;

在react中,我們應(yīng)該如何獲取真實的DOM元素呢?利用ref屬性,首先導(dǎo)入createRef方法,創(chuàng)建一個storeyRef,然后將該storeyRef綁定到組件容器元素上,即:

const storeyRef = createRef<HTMLDivElement>();
//...
<StyleStorey ref={storeyRef}></StyleStorey>

然后我們就可以使用useEffect方法,也就是react hooks中的一個生命周期鉤子函數(shù),監(jiān)聽這個storeyRef,如果監(jiān)聽到了,就可以直接拿到dom元素,并且使用一個狀態(tài)來存儲高度值。即:

const [offset,setOffset] = useState(0)
//...
useEffect(() => {
    //storeyRef.current顯然就是我們實際拿到的DOM元素
    if(storeyRef){
      setOffset(storeyRef.current?.offsetHeight as number)
    }
},[storeyRef])

樓層列表渲染

接下來,我們來看樓層數(shù)的動態(tài)生成,我們知道在react中動態(tài)生成列表元素,實際上就是使用數(shù)組的map方法,因此我們要根據(jù)count來生成一個數(shù)組,在這里,我們可以生成一個key數(shù)組,但是由于我們要控制按鈕的禁用,因此額外添加一個disabled屬性,因此這也是以下代碼的意義:

export interface StoreyItem {
   key: string
   disabled: boolean
}
const [storeyList, setStoreyList] = useState<StoreyItem []>();
useComponentDidMount(() => {
    let res: StoreyItem [] = [];
    for (let i = count - 1; i >= 0; i--) {
      res.push({
        key: String(i + 1),
        disabled: false
      });
    }
    setStoreyList(res);
});

這里涉及到一個模擬useComponentDidMount鉤子函數(shù),很簡單,在hooks目錄下新建一個useComponentDidMount.ts,然后寫上如下代碼:

import { useEffect } from 'react';
const useComponentDidMount = (onMountHandler: (...args:any) => any) => {
  useEffect(() => {
    onMountHandler();
  }, []);
};
export default useComponentDidMount;

然后就是渲染樓層組件,如下:

(
    <>
      {storeyList?.map((item,index) => (
        <StyleStorey key={item.key} ref={storeyRef}>
          <StyleStoreyController>
            <StyleButton
              disabled={Number(item.key) === storeyList.length || item.disabled}
              onClick={() => onClickHandler(item.key,index,'onUp')}
              className={`${item.key === checked && type === 'onUp' ? "checked" : ""}`}
            >
              ↑
            </StyleButton>
            <StyleButton
              disabled={Number(item.key) === 1 || item.disabled}
              onClick={() => onClickHandler(item.key,index,'onDown')}
              className={`${item.key === checked && type === 'onDown' ? "checked" : ""}`}
            >
              ↓
            </StyleButton>
          </StyleStoreyController>
          <StyleStoreyCount>{item.key}</StyleStoreyCount>
        </StyleStorey>
      ))}
    </>
);

<></>是React.Fragment的一種寫法,可以理解它就是一個占位標(biāo)簽,沒有什么實際含義,storeyList默認(rèn)值是undefined,因此需要加?代表可選鏈,這里包含了兩個部分,第一個部分就是控制按鈕,第二部分就是顯示樓層數(shù)。

實際上我們生成的元素數(shù)組中的key就是樓層數(shù),這也是這行代碼的意義:

<StyleStoreyCount>{item.key}</StyleStoreyCount>

還有就是react在生成列表的時候,需要綁定一個key屬性,方便虛擬DOM,diff算法的計算,這里不用多講。接下來我們來看按鈕組件的邏輯。

樓層按鈕組件

按鈕組件的邏輯包含三個部分:

  • 禁用效果
  • 點(diǎn)擊使得電梯上升和下降
  • 選中效果

我們知道最高樓的上升是無法上升的,所以需要禁用,同樣的,底樓的下降也是需要禁用的,所以這兩行代碼就是這個意思:

Number(item.key) === storeyList.length
Number(item.key) === 1

接下來還有一個條件,這個item.disabled其實主要是防止重復(fù)點(diǎn)擊的問題,當(dāng)然這并不是一個好的解決辦法,但目前來說我們先這樣做,定義一個type狀態(tài),代表是點(diǎn)擊的上升還是下降:

//接口類型,type應(yīng)只能是onUp或者onDown,代表只能是上升還是下降
export interface MethodProps {
  onUp(v: number, t: number, h?: number): void;
  onDown(v: number, t: number, h?: number): void;
}
const [type, setType] = useState<keyof MethodProps>();

然后定義一個checked狀態(tài),代表當(dāng)前按鈕是否選中:

//checked存儲key值,所以
const [checked, setChecked] = useState<string>();
className={`${item.key === checked && type === 'onUp' ? "checked" : ""}`}
className={`${item.key === checked && type === 'onDown' ? "checked" : ""}`}

需要注意的就是這里的判斷:

item.key === checked && type === 'onUp' //以及 ${item.key === checked && type === 'onDown'

我們樣式當(dāng)中是添加了checked的,這個沒什么好說的。

然后,我們還需要緩存當(dāng)前樓層是哪一樓,因為下次點(diǎn)擊的時候,我們就需要根據(jù)當(dāng)前樓層來計算,而不是重頭開始。

const [currentFloor, setCurrentFloor] = useState(1);

最后,就是我們的點(diǎn)擊上升和下降邏輯,還是有點(diǎn)復(fù)雜的:

const onClickHandler = (key: string,index:number,method: keyof MethodProps) => {
    setChecked(key)
    setType(method)
    const moveFloor = count - index
    const diffFloor = Math.abs(moveFloor - currentFloor)
    setCurrentFloor(moveFloor)    
    props[method]?.(diffFloor, offset * (moveFloor - 1))
    // 也許這不是一個好的方法
    if(+key !== storeyList?.length && +key !== 1){
        setStoreyList((storey) => storey?.map(item => ({ ...item,disabled: true })))
    }
    setTimeout(() => {
      setChecked(void 0);
      if(+key !== storeyList?.length && +key !== 1){
        setStoreyList((storey) => storey?.map(item => ({ ...item,disabled: false })))
      }
    }, diffFloor * 1000);
};

該函數(shù)有三個參數(shù),第一個代表當(dāng)前樓層的key值,也就是樓層數(shù),第二個代表當(dāng)前樓的索引,注意索引和樓層數(shù)是不一樣的,第三個就是點(diǎn)擊的是上升還是下降。我們的第一個參數(shù)和第三個參數(shù)是用來設(shè)置按鈕的選中效果,即:

setChecked(key)
setType(method)

接下來,我們需要計算動畫的執(zhí)行時間,例如我們從第一層到第五層,如果按每秒到一層來計算,那么第一層到第五層就需要4s的時間,同理我們的偏移量就應(yīng)該是每層樓高與需要移動的樓高在減去1。因此,計算需要移動的樓高我們是:

const moveFloor = count - index
const diffFloor = Math.abs(moveFloor - currentFloor)
//設(shè)置當(dāng)前樓層
setCurrentFloor(moveFloor) 
props[method]?.(diffFloor, offset * (moveFloor - 1))

注意我們是將事件拋給父組件的,因為我們的電梯組件和樓層容器組件在同一層級,只有這樣,才能設(shè)置電梯組件的樣式。即:

//傳入兩個參數(shù),代表動畫的執(zhí)行時間和偏移量,props[method]其實就是動態(tài)獲取props中的屬性
props[method]?.(diffFloor, offset * (moveFloor - 1))

然后這里的邏輯就是防止重復(fù)點(diǎn)擊的代碼,這并不是一個好的解決方式:

if(+key !== storeyList?.length && +key !== 1){
    setStoreyList((storey) => storey?.map(item => ({ ...item,disabled: true })))
}
setTimeout(() => {
    //...
    if(+key !== storeyList?.length && +key !== 1){
      setStoreyList((storey) => storey?.map(item => ({ ...item,disabled: false })))
    }
}, diffFloor * 1000);

修改樓層容器組件

好了,這個組件的分析就到此為止了,我們既然把事件拋給了父組件,因此我們還需要修改一下它的父組件StoreyZone.tsx的代碼,如下:

import styled from "@emotion/styled";
import Storey from "./Storey";

const StyleStoreyZone = styled.div`
  width: auto;
  height: 100%;
`;
export interface StoreyZoneProps {
  onUp(v: number, h?: number): void;
  onDown(v: number, h?: number): void;
}
const StoreyZone = (props: Partial<StoreyZoneProps>) => {
  const { onUp, onDown } = props;
  return (
    <StyleStoreyZone>
      <Storey
        onUp={(k: number, h: number) => onUp?.(k, h)}
        onDown={(k: number, h: number) => onDown?.(k, h)}
      />
    </StyleStoreyZone>
  );
};

export default StoreyZone;

然后就是最后的ElevatorBuild.tsx組件的修改,如下:

import styled from "@emotion/styled";
import { useState } from "react";
import { StyleProps } from "../type/style";
import ElevatorShaft from "./ElevatorShaft";
import StoreyZone from "./StoreyZone";

const StyleBuild = styled.div`
  width: 350px;
  max-width: 100%;
  min-height: 500px;
  border: 6px solid var(--elevatorBorderColor--);
  overflow: hidden;
  display: flex;
  margin: 3vh auto;
`;

const ElevatorBuild = () => {
  const [elevatorStyle, setElevatorStyle] = useState<StyleProps["style"]>();
  const [doorStyle, setDoorStyle] = useState<StyleProps["style"]>();
  const [open,setOpen] = useState(false)
  const move = (diffFloor: number, offset: number) => {
    setElevatorStyle({
      transitionDuration: diffFloor + 's',
      bottom: offset,
    });
    setOpen(true)
    setDoorStyle({
      animationDelay: diffFloor + 's'
    });

    setTimeout(() => {
        setOpen(false)
    },diffFloor * 1000 + 3000)
  };
  return (
    <StyleBuild>
      <ElevatorShaft
        elevatorStyle={elevatorStyle}
        leftDoorStyle={doorStyle}
        rightDoorStyle={doorStyle}
        leftToggle={open}
        rightToggle={open}
      ></ElevatorShaft>
      <StoreyZone onUp={(k: number,h: number) => move(k,h)} onDown={(k: number,h: number) => move(k,h)}></StoreyZone>
    </StyleBuild>
  );
};

export default ElevatorBuild;

最后,我們來檢查一下代碼,看看還有沒有什么可以優(yōu)化的地方,可以看到我們的按鈕禁用邏輯是可以復(fù)用的,我們再重新創(chuàng)建一個函數(shù),即:

const changeButtonDisabled = (key:string,status: boolean) => {
    if(+key !== storeyList?.length && +key !== 1){
      setStoreyList((storey) => storey?.map(item => ({ ...item,disabled: status })))
    }
}

const onClickHandler = (key: string,index:number,method: keyof MethodProps) => {
    //...
    changeButtonDisabled(key,true)
    setTimeout(() => {
      //...
      changeButtonDisabled(key,false)
    }, diffFloor * 1000);
};

到此為止,我們的一個電梯小程序就算是完成了,也不算是復(fù)雜,總結(jié)一下我們學(xué)到的知識點(diǎn):

  • css in js 我們使用的是@emotion這個庫
  • 父子組件的通信,使用props
  • 操作DOM,使用ref
  • 組件內(nèi)的狀態(tài)通信,使用useState,以及如何修改狀態(tài),有兩種方式
  • 鉤子函數(shù)useEffect
  • 類名的操作與事件還有就是樣式的設(shè)置
  • React列表渲染
  • typescript接口的定義,以及一些內(nèi)置的類型

最后

當(dāng)然這里我們還可以擴(kuò)展,比如樓層數(shù)的限制,再比如添加門開啟后,里面的美女真的走出來的動畫效果,如有興趣可以參考源碼自行擴(kuò)展。

以上就是利用React實現(xiàn)一個有點(diǎn)意思的電梯小程序的詳細(xì)內(nèi)容,更多關(guān)于React電梯小程序的資料請關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

  • react開發(fā)教程之React 組件之間的通信方式

    react開發(fā)教程之React 組件之間的通信方式

    本篇文章主要介紹了react開發(fā)教程之React組件通信詳解,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧
    2017-08-08
  • 瀏覽器中視頻播放器實現(xiàn)的基本思路與代碼

    瀏覽器中視頻播放器實現(xiàn)的基本思路與代碼

    這篇文章主要給大家介紹了關(guān)于瀏覽器中視頻播放器實現(xiàn)的基本思路與代碼,并且詳細(xì)總結(jié)了瀏覽器中的音視頻知識,對大家的理解和學(xué)習(xí)非常有幫助,需要的朋友可以參考下
    2021-08-08
  • React?hook實現(xiàn)簡單的websocket封裝方式

    React?hook實現(xiàn)簡單的websocket封裝方式

    這篇文章主要介紹了React?hook實現(xiàn)簡單的websocket封裝方式,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教
    2022-09-09
  • React中useState值為對象時改變值不渲染問題

    React中useState值為對象時改變值不渲染問題

    這篇文章主要介紹了React中useState值為對象時改變值不渲染問題,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教
    2023-02-02
  • React實現(xiàn)頁面狀態(tài)緩存(keep-alive)的示例代碼

    React實現(xiàn)頁面狀態(tài)緩存(keep-alive)的示例代碼

    因為?react、vue都是單頁面應(yīng)用,路由跳轉(zhuǎn)時,就會銷毀上一個頁面的組件,但是有些項目不想被銷毀,想保存狀態(tài),本文給大家介紹了React實現(xiàn)頁面狀態(tài)緩存(keep-alive)的代碼示例,需要的朋友可以參考下
    2024-01-01
  • React Native 自定義下拉刷新上拉加載的列表的示例

    React Native 自定義下拉刷新上拉加載的列表的示例

    本篇文章主要介紹了React Native 自定義下拉刷新上拉加載的列表的示例,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧
    2018-03-03
  • React中常見的動畫實現(xiàn)的幾種方式

    React中常見的動畫實現(xiàn)的幾種方式

    本篇文章主要介紹了React中常見的動畫實現(xiàn)的幾種方式,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧
    2018-01-01
  • React?Refs?的使用forwardRef?源碼示例解析

    React?Refs?的使用forwardRef?源碼示例解析

    這篇文章主要為大家介紹了React?之?Refs?的使用和?forwardRef?的源碼解讀,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2022-11-11
  • 淺談react受控組件與非受控組件(小結(jié))

    淺談react受控組件與非受控組件(小結(jié))

    本篇文章主要介紹了淺談react受控組件與非受控組件(小結(jié)),小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧
    2018-02-02
  • React項目build打包頁面空白的解決方案

    React項目build打包頁面空白的解決方案

    React項目執(zhí)行build命令后,在本地服務(wù)器打開頁面是空白的,本文主要介紹了React項目build打包頁面空白的解決方案,感興趣的可以了解一下
    2023-08-08

最新評論