查看成果
咱们先来看一下明天要实现的示例的成果,如下所示:
好,接下来咱们也看到了这个示例的成果,让咱们进入正题,开始欢快的编码吧。
技术栈介绍
这个小程序,咱们将采纳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-elevatornpm installnpm run dev
查看一下咱们初始化我的项目是否胜利。
特地申明: 请留神装置了node.js和npm工具
css in js
能够看到,咱们的我的项目初始化曾经实现,好,接下来,咱们还要额定的装一些我的项目当中遇到的依赖,例如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还反对对象写法,然而咱们这里基本上只用模板字符串语法就够了。
接下来步入正题,咱们首先删除初始化的一些代码,因为咱们没有必要用到。
分析程序的构造
好删除之后,咱们接下来看一下咱们要实现的电梯小程序的构造:
- 电梯井(也就是电梯回升或者降落的中央)
- 电梯
- 电梯门(分为左右门)
- 楼层
4.1 楼层数
4.2 楼层按钮(蕴含回升和降落按钮)
构造好了之后,接下来咱们来看看有哪些性能:
- 点击楼层,催动电梯回升或者降落
- 电梯达到对应楼层,电梯左右门关上
- 门关上之后,外面的美女就进去啦
- 按钮会有一个点击选中的成果
咱们先来剖析构造,依据以上的拆分,咱们能够大抵将整个小程序分成如下几个组件:
- 楼房(容器组件)
- 电梯井组件
2.1 电梯组件
2.1.1 电梯右边的门
2.1.1 电梯左边的门 - 楼层组件
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 ElevatorLeftDoor
Partial是一个泛型,传入接口,代表将接口的每个属性变成可选属性,依据这个原理,咱们能够得悉左边门的组件代码也很相似。如下:
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 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;
然而别忘了咱们这里的电梯组件因为须要回升和降落,因而还须要设置款式,再次批改一下电梯组件的代码如下:
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.lengthNumber(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 - indexconst 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接口的定义,以及一些内置的类型
最初
当然这里咱们还能够扩大,比方楼层数的限度,再比方增加门开启后,外面的美女真的走进去的动画成果,如有趣味能够参考源码自行扩大。
在线示例
最初谢谢大家观看,如果感觉本文有帮忙到你,望不悭吝点赞和珍藏,动动小手点star,嘿嘿。
特地申明: 这个小示例只适宜老手学习,大佬就算了,这个小程序对大佬来说很简略。