React是FaceBook于2013开源的项目,一经问世就在前端占据一席之地,随着近几年不断发展壮大,已成为前端主流框架。而它之所以能如此成功,主要是因为它的声明式
、组件化
、高效(虚拟DOM、Diff)
与便捷(React Native)
,下面通过项目实战详细地介绍组件的设计思路与流程,如发现各种问题,请看官巨佬们轻喷并指正~🙏
首先我们需要创建一个React项目,我使用的是 ViteJS 来初始化本项目,当然也可以使用官方脚手架 create-react-app 来初始化,前者相对来说更快,两者各有优缺点,大家可以根据喜好选择。在VSCode终端输入:
npm init @vitejs/app
接着给项目命名为 Tesla --> 连续两次选择 react --> 最后根据提示进入创建的项目文件夹 --> 安装项目依赖 --> 运行下我们的项目
cd Teslanpm install
npm run dev
紧接着我们会在3000端口看到项目已经运行起来了,最后再来介绍下本项目依赖的包:
axios
: 是一个基于 promise 的一个用于发送ajax请求的HTTP库,本质上是对AJAX的封装,用于前端获取数据。styled-components
:是一个针对React的 css in js 类库。antd-mobile
:提供可直接使用的前端模板,如本项目用到的Modal模态框、PopUp弹出层等。iconfont
:图标字体库。propTypes
:对props中数据类型进行检测及限制。在终端命令行输入以下命令,将上面提到的包全部下载安装:
npm i axiosnpm i styled-componentsnpm i antd-mobilenpm i prop-types
另外,项目的数据存储在fastmock,这是一个在线接口工具,可以让你在没有后端程序的情况下能真实地在线模拟 ajax 请求,实现前后端分离。
首先来看下界面实现的效果图: 项目预览
通过观察Tesla 官网,可以了解到我们需要实现以下功能:(该项目仅为1.0版本,更多功能后续后慢慢添加)
下面来看看项目文件结构:
采用Layout布局,将
和组件分别固定在头部与尾部,页面级别组件
放在中间,简化App.jsx
根组件。
根组件负责调用api获取数据,并通过props
向子组件传递参数。(由于只有一个页面组件,所以没有将路由分离到单独的文件,后期页面组件多了则需要分离)
import React, { useState, useEffect } from 'react'
import './App.css'
import { Routes, Route, Link } from 'react-router-dom'
import Custom from './pages/Custom'
import Header from './components/Header'
import Footer from './components/Footer'
import { getCarParams } from './api/request'
function App() {// 设置一个汽车参数状态并设初始值为空数组,carParams为读操作,setCarParams为写操作const [carParams, setCarParams] = useState([]);// 设置一个显示版本状态并设初始值为1,showEdition为读操作,setShowEdition为写操作const [showEdition, setShowEdition] = useState('1')// useEffect生命周期函数,它会在组件加载完成时运行,这里作用是异步(async + await)获取数据useEffect(() => {(async () => {let { data: carParamsData } = await getCarParams()setCarParams(carParamsData)})()}, [])return ( }> )
}
export default App
引入axios 远程获取数据,这是采用了fastmock创建的伪接口,后续根组件通过引入并调用getCarParams方法拿到数据。
import axios from 'axios'
export const getCarParams = () =>axios.get('https://www.fastmock.site/mock/7c2b4d662d21c3311479338632d3faec/tesla/design')
定制页面组件将根组件传来的参数props
继续传给 Main.jsx 子组件。(由于时间有限,只写了主组件,定制界面的其他组件后期添加)
import React, { useState, useEffect } from 'react'
import { Wrapper } from './style'
import Main from './Main'
export default function Custom(props) {return ( )
}
获取
组件传来的数据,通过判断当前组件是否选中,是则渲染数据,不是则不渲染,并引入
组件,同时引入propTypes对props
中数据类型进行检测,判断是否为数组,且必需传值
import propTypes from 'prop-types'
...
{/* 汽车图片 */}...{/* 汽车参数 */}{carParams.map(item => {return ({/* 通过判断当前组件是否选中,是则渲染数据,不是则不渲染 */}showEdition == item.id &&...)})}{/* 电机驱动部分 */}...{/* 模态框部分 */}
...
// 判断是否为数组,且必需传值
Main.propTypes = {carParams: propTypes.array.isRequired
}
头组件只用来放置 Logo,通过定位,固定在头部。
import React from 'react'
import { Wrapper } from './style'
import IMG from '../../assets/img/tesla.png'
import { Link } from 'react-router-dom'
export default function Header() {return ( )
}
尾组件存放基础结构、
计算器弹层组件与
弹出层组件,通过定位,固定在尾部。
{/* 点击显示模态框 */} setVisible(true)}> setVisible(true)}>{carParams.map(item => {return (showEdition == item.id && ¥ {item.price})})}实际价格{carParams.map(item => {return (showEdition == item.id && ¥ {item.another_price})})}减去节省的燃油费{/* 对话框弹出层组件 */} {/* 计算器弹层 */}
引入了上面提到的antd-mobile 组件库中的Popup弹出层组件
// 引入 antd-mobile 组件库
import { Popup, Button } from 'antd-mobile'
...
{filter: 'blur(5px)',opacity: 0.5,}}// 主体样式bodyStyle={{borderTopLeftRadius: '12px',borderTopRightRadius: '12px',minHeight: '40vh',}}// 点击关闭图标关闭弹出层onClose={() => {setVisible1(false)}}// 点击遮罩层关闭弹出层onMaskClick={() => {setVisible1(false)}}>// 弹出层内容{mockContent()}
...
const mockContent = () => {return (... )
}
引入了上面提到的antd-mobile 组件库中的Modal弹层组件,通过结构父组件传递的参数props
,实现tab 切换。
import React from 'react'
import Modal from '@/components/common/Modal/ModalCalculator/modal'
import './index.css'
import { useState } from 'react'
export default function ModalCalculator(props) {// 将三个参数从父组件传递的参数props 中解构出来const { visible, setVisible, onModalClose } = props// 设置一个 active 状态,并设初始值为1,用来给下面tab 切换做准备const [active, setActive] = useState('1') return ( setVisible(false)}>{* tab 切换实现 */} setActive('1')}>现金 setActive('2')}>合作金融机构贷款 setActive('3')}>特斯拉融资租赁{active == '1' &&...}{active == '2' &&...}{active == '3' &&...})
}
由于样式不同,模态框无法复用 (应该是我的原因),导致
组件需要与
组件分开写,但是即使分开,如果className相同,也会相互影响,所以我干脆 结构分离 + 类名分离,Modal组件的 {title} 和 {children} 则对应
组件与
组件的 标题 与 内容 ,
组件 (该组件对应
组件) 如下:
import React, { useState, useEffect } from "react";
import './modal.css'
const Modal = (props) => {const [visible, setVisible] = useState(false)const { visible: show, children, title, onClose } = props;useEffect(() => {setVisible(show)}, [show])const maskClick = () => {setVisible(false)onClose && onClose()}return (visible && {title}{children})
}
export default Modal;
剩下的
组件以及对应的
组件就不放图了,与上面类似,只是改了下类名。