本篇內容介紹了“怎么利用React實現一個電梯小程序”的有關知識,在實際案例的操作過程中,不少人都會遇到這樣的困境,接下來就讓小編帶領大家學習一下如何處理這些情況吧!希望大家仔細閱讀,能夠學有所成!
我們先來看一下今天要實現的示例的效果,如下所示

好,接下來我們也看到了這個示例的效果,讓我們進入正題,開始愉快的編碼吧。
這個小程序,我們將采用React + typescript + css in js語法編寫,并且采用最新比較流行的工具vite來構建。
我們可以選擇在電腦按住shift,然后右鍵,選擇powershell,也就是默認的系統終端。然后輸入命令:
mkdir react-elevator
創建一個目錄,創建好之后,接著我們在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,我們需要安裝@emotion/styled,@emotion/react依賴。繼續輸入命令:
npm install @emotion/styled @emotion/react --save-dev
安裝好之后,我們在項目里面使用一下該語法。
首先引入styled,如下:
import styled from "@emotion/styled"
接著創建一個樣式組件,css in js實際上就是把每個組件當成一個樣式組件,我們可以通過styled后面跟html標簽名,然后再跟模板字符串,結構如下:
const <組件名> = styled.<html標簽名>` //這里寫樣式代碼 `
例如:
const Link = styled.a` color:#fff; `
以上代碼就是寫一個字體顏色為白色的超鏈接組件,然后我們就可以在jsx當中直接寫link組件。如下所示:
<div> <Link>這是一個超鏈接組件</Link> </div>
當然emotion還支持對象寫法,但是我們這里基本上只用模板字符串語法就夠了。
接下來步入正題,我們首先刪除初始化的一些代碼,因為我們沒有必要用到。
好刪除之后,我們接下來看一下我們要實現的電梯小程序的結構:
1.電梯井(也就是電梯上升或者下降的地方)
2.電梯
3.電梯門(分為左右門)
4.樓層
4.1 樓層數
4.2 樓層按鈕(包含上升和下降按鈕)
結構好了之后,接下來我們來看看有哪些功能:
點擊樓層,催動電梯上升或者下降
電梯到達對應樓層,電梯左右門打開
門打開之后,里面的美女就出來啦
按鈕會有一個點擊選中的效果
我們先來分析結構,根據以上的拆分,我們可以大致將整個小程序分成如下幾個組件:
1.樓房(容器組件)
2.電梯井組件
2.1 電梯組件
2.1.1 電梯左邊的門
2.1.1 電梯右邊的門
3.樓層組件
3.1 樓層控制組件
3.1.1 樓層上升按鈕組件
3.1.2 樓層下降按鈕組件
3.2 樓層數組件
我們先來寫好組件和樣式,然后再完成功能。
首先是我們的樓房組件,我們新建一個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當中引入,并使用它:
//這里是新增的代碼
import ElevatorBuild from "./components/ElevatorBuild"
const App = () => (
<div className="App">
{/*這里是新增的代碼 */}
<ElevatorBuild />
</div>
)
export default App在這里,我們定義了全局css變量樣式,因此在當前目錄下創建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;
}接下來,讓我們繼續完成電梯井組件,同樣在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 }由于我們功能會需要設置這兩個組件的樣式,并且我們這個樣式是設置在style屬性上的,因此我們可以通過props來傳遞,現在我們先寫好typescript接口類,創建一個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 ElevatorLeftDoorPartial是一個泛型,傳入接口,代表將接口的每個屬性變成可選屬性,根據這個原理,我們可以得知右邊門的組件代碼也很類似。如下:
import { StyleProps } from '../type/style';
import { StyleRightDoor } from './Door'
const ElevatorRightDoor = (props: Partial<StyleProps>) => {
const { style } = props;
return (
<StyleRightDoor style={style}/>
)
}
export default ElevatorRightDoor;這兩個組件寫好之后,我們接下來要在電梯組件里引入并使用它們,由于功能邏輯會需要設置樣式,因此,我們通過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完成了電梯組件之后,接下來我們在電梯井組件里面引入電梯組件,注意這里后續邏輯我們會設置電梯組件和電梯門組件的樣式,因此在電梯井組件中,我們需要通過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我們可以看到,當到達一定時間,電梯門會有開啟動畫,這里我們顯然沒有加上,所以我們可以為電梯門各自加一個是否開啟的props用來傳遞,繼續修改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語法可以通過函數來返回一個css屬性,從而達到動態設置屬性的目的,一對尖括號,其實也就是typescript中的泛型,代表是否傳入toggle數據,接下來修改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 ElevatorLeftDoorimport { 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 Elevatorimport 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;但是別忘了我們這里的電梯組件因為需要上升和下降,因此還需要設置樣式,再次修改一下電梯組件的代碼如下:
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到目前為止,我們的左半邊部分已經完成了,接下來,我們來完成右半邊部分的樓層數和控制按鈕組件,我們的樓層是動態生成的,因此我們需要一個容器組件包裹起來,先寫這個樓層容器組件,如下所示:
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;可以看到樓層組件的邏輯非常多,但其實一項一項的分析下來也并不難。
接下來,我們在該容器組件中引入,并且將該組件在樓房組件中引入,就可以得到我們整個電梯小程序的結構了。
在這里我們來一步一步的分析樓層組件的邏輯,
首先樓層是動態生成的,通過父組件傳遞,因此我們在props當中定義一個count,默認值是6,代表默認生成的樓層數。這也就是我們這行代碼的意義:
export interface StoreyProps extends MethodProps{
count: number
}
const { count = 6 } = props;其次我們在對電梯進行上升和下降的時候,需要獲取到每一層樓高,實際上也就是樓層容器元素的高度,如何獲取DOM元素的實際高度?我們先想一下,如果是一個真實的DOM元素,我們只需要獲取offsetHeight就行了,即:
//這里的el顯然是一個dom元素 const offset: number = el.offsetHeight;
在react中,我們應該如何獲取真實的DOM元素呢?利用ref屬性,首先導入createRef方法,創建一個storeyRef,然后將該storeyRef綁定到組件容器元素上,即:
const storeyRef = createRef<HTMLDivElement>();
//...
<StyleStorey ref={storeyRef}></StyleStorey>然后我們就可以使用useEffect方法,也就是react hooks中的一個生命周期鉤子函數,監聽這個storeyRef,如果監聽到了,就可以直接拿到dom元素,并且使用一個狀態來存儲高度值。即:
const [offset,setOffset] = useState(0)
//...
useEffect(() => {
//storeyRef.current顯然就是我們實際拿到的DOM元素
if(storeyRef){
setOffset(storeyRef.current?.offsetHeight as number)
}
},[storeyRef])接下來,我們來看樓層數的動態生成,我們知道在react中動態生成列表元素,實際上就是使用數組的map方法,因此我們要根據count來生成一個數組,在這里,我們可以生成一個key數組,但是由于我們要控制按鈕的禁用,因此額外添加一個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鉤子函數,很簡單,在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的一種寫法,可以理解它就是一個占位標簽,沒有什么實際含義,storeyList默認值是undefined,因此需要加?代表可選鏈,這里包含了兩個部分,第一個部分就是控制按鈕,第二部分就是顯示樓層數。
實際上我們生成的元素數組中的key就是樓層數,這也是這行代碼的意義:
<StyleStoreyCount>{item.key}</StyleStoreyCount>還有就是react在生成列表的時候,需要綁定一個key屬性,方便虛擬DOM,diff算法的計算,這里不用多講。接下來我們來看按鈕組件的邏輯。
按鈕組件的邏輯包含三個部分:
禁用效果
點擊使得電梯上升和下降
選中效果
我們知道最高樓的上升是無法上升的,所以需要禁用,同樣的,底樓的下降也是需要禁用的,所以這兩行代碼就是這個意思:
Number(item.key) === storeyList.length Number(item.key) === 1
接下來還有一個條件,這個item.disabled其實主要是防止重復點擊的問題,當然這并不是一個好的解決辦法,但目前來說我們先這樣做,定義一個type狀態,代表是點擊的上升還是下降:
//接口類型,type應只能是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狀態,代表當前按鈕是否選中:
//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'我們樣式當中是添加了checked的,這個沒什么好說的。
然后,我們還需要緩存當前樓層是哪一樓,因為下次點擊的時候,我們就需要根據當前樓層來計算,而不是重頭開始。
const [currentFloor, setCurrentFloor] = useState(1);
最后,就是我們的點擊上升和下降邏輯,還是有點復雜的:
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);
};該函數有三個參數,第一個代表當前樓層的key值,也就是樓層數,第二個代表當前樓的索引,注意索引和樓層數是不一樣的,第三個就是點擊的是上升還是下降。我們的第一個參數和第三個參數是用來設置按鈕的選中效果,即:
setChecked(key) setType(method)
接下來,我們需要計算動畫的執行時間,例如我們從第一層到第五層,如果按每秒到一層來計算,那么第一層到第五層就需要4s的時間,同理我們的偏移量就應該是每層樓高與需要移動的樓高在減去1。因此,計算需要移動的樓高我們是:
const moveFloor = count - index const diffFloor = Math.abs(moveFloor - currentFloor) //設置當前樓層 setCurrentFloor(moveFloor) props[method]?.(diffFloor, offset * (moveFloor - 1))
注意我們是將事件拋給父組件的,因為我們的電梯組件和樓層容器組件在同一層級,只有這樣,才能設置電梯組件的樣式。即:
//傳入兩個參數,代表動畫的執行時間和偏移量,props[method]其實就是動態獲取props中的屬性 props[method]?.(diffFloor, offset * (moveFloor - 1))
然后這里的邏輯就是防止重復點擊的代碼,這并不是一個好的解決方式:
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;最后,我們來檢查一下代碼,看看還有沒有什么可以優化的地方,可以看到我們的按鈕禁用邏輯是可以復用的,我們再重新創建一個函數,即:
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);
};到此為止,我們的一個電梯小程序就算是完成了,也不算是復雜,總結一下我們學到的知識點:
css in js 我們使用的是@emotion這個庫
父子組件的通信,使用props
操作DOM,使用ref
組件內的狀態通信,使用useState,以及如何修改狀態,有兩種方式
鉤子函數useEffect
類名的操作與事件還有就是樣式的設置
React列表渲染
typescript接口的定義,以及一些內置的類型
“怎么利用React實現一個電梯小程序”的內容就介紹到這里了,感謝大家的閱讀。如果想了解更多行業相關的知識可以關注億速云網站,小編將為大家輸出更多高質量的實用文章!
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。