前言
在现代Web开发中,React凭借其组件化开发和声明式编程的特性,已成为构建交互式用户界面的首选框架。本文将带您系统了解React项目开发的完整流程,从项目初始化到复杂组件实现,涵盖以下核心内容:
-
项目搭建与环境配置
通过Vite快速创建React项目,解析
dependencies
与devDependencies
的本质区别,掌握生产环境与开发环境的依赖管理策略。 -
表单组件开发范式
深度解析受控组件与非受控组件的实现差异,通过代码示例演示状态驱动(
useState
+onChange
)与默认值控制(defaultValue
)两种开发模式的应用场景。 -
复杂组件实战:日历控件
从零实现一个完整的月视图日历组件,涵盖以下技术要点:
- 日期计算逻辑(月份切换、日期填充)
- 组件状态管理与外部通信(
defaultValue
初始化 +onChange
回调) - CSS Flex布局实现网格日历视图
- 交互优化(选中态反馈、悬停效果)
日期api
1. 创建日期对象
方法 | 说明 |
---|---|
new Date() |
创建当前日期和时间 |
new Date(timestamp) |
通过时间戳创建(毫秒数,1970-01-01起) |
new Date("2024-07-07") |
通过日期字符串创建(ISO格式推荐) |
new Date(2024, 6, 7, 12, 30) |
通过年、月、日、时、分创建(月从0开始) |
2. 获取日期组件
方法 | 返回值范围 | 说明 |
---|---|---|
.getFullYear() |
4位数年份 | 如 2024 |
.getMonth() |
0-11 | 0=1月, 11=12月 |
.getDate() |
1-31 | 月中的第几天 |
.getDay() |
0-6 | 0=周日, 1=周一 |
.getHours() |
0-23 | 小时 |
.getMinutes() |
0-59 | 分钟 |
.getSeconds() |
0-59 | 秒 |
.getMilliseconds() |
0-999 | 毫秒 |
.getTime() |
时间戳 | 1970年至今的毫秒数 |
UTC 版本 :
如 .getUTCHours()
、.getUTCMonth()
等,用法与本地时间相同。
3. 设置日期组件
方法 | 说明 |
---|---|
.setFullYear(year) |
设置年份 |
.setMonth(month) |
设置月份(0-11) |
.setDate(day) |
设置月中日期 |
.setHours(hours) |
设置小时(可链式设置) |
.setTime(timestamp) |
通过时间戳设置 |
注意 :设置方法会直接修改原 Date
对象。
4. 日期格式化
方法 | 示例输出 | 说明 |
---|---|---|
.toString() |
"Sun Jul 07 2024 12:30:00 GMT+0800" |
完整日期字符串 |
.toDateString() |
"Sun Jul 07 2024" |
仅日期部分 |
.toTimeString() |
"12:30:00 GMT+0800" |
仅时间部分 |
.toISOString() |
"2024-07-07T04:30:00.000Z" |
ISO标准格式 |
.toLocaleString() |
"2024/7/7 12:30:00" |
本地化格式 |
.toLocaleDateString() |
"2024/7/7" |
本地化日期 |
.toLocaleTimeString() |
"12:30:00" |
本地化时间 |
5. 其他实用方法
方法 | 说明 |
---|---|
Date.now() |
返回当前时间戳(毫秒) |
Date.parse("2024-07-07") |
解析字符串返回时间戳 |
.valueOf() |
等价于 .getTime() |
401未经授权
创建项目
npx create-react-app xxxx
使用vite来创建项目
使用前需要安装vite
使用npm install -g vite
第一种方式npm create vite@latest my-app -- --template react 第二种方式npm create-vite

什么是依赖?依赖的种类?
dependencies
- 生产依赖:项目开发完成后,denpendencies中的第三方源代码也会被打包进来
devDependencies
- 开发依赖:只在项目开发过程中有意义,devDependencies中的第三方源代码不会被打包进最终的项目中
在创建项目的终端执行npm i
自动识别我们需要的依赖并且帮我们安装
终端使用npm run dev运行项目

antd寻找我们需要的组件样式
什么是受控模式和非受控模式?
受控模式 vs 非受控模式
- 表单中的 input
- 用户修改 input 值
- 代码修改 input 值
- 能用代码设置表单的初始值,但是无法再次修改 value,能修改value的只有用户,这种就叫做 非受控模式
受控模式
-
通常情况下,不建议使用受控模式 每次都会重新渲染组件,造成过多性能消耗
-
当需要对输入的值进行特殊处理,处理完在设置到表单或则要实时处理,把状态同步到组件的时候用受控组件
js
import { useState } from "react";
function App() {
const [value, setValue] = useState("hello");
console.log('App');
function onChange(e) {
// setValue(e.target.value);
setValue(e.target.value.toUpperCase());
console.log(e.target.value);
}
return <input type="text" value={value} onChange={onChange} />;
}
//声明死了value,需要通过代码setvalue()更改值
export default App;
//受控模式
js
import { useEffect, useState } from "react";
async function queryData() {
const data = await new Promise((resolve) => {
setTimeout(()=>{
resolve(666)
},2000)
});
return data;
}
function App() {
const [num,setNum]=useState(0)
useEffect(()=>{
queryData().then(res=>{
setNum(res)
})
},[])
return(
<div>{num}</div>
)
}
export default App;
//受控模式
非受控模式
表单defaultValue默认属性值设为hello word
js
function App() {
function onChange(e) {
console.log(e.target.value);//事件对象 表单对象身上的值
}
return <input type="text" defaultValue={"hello world"} onChange={onChange}/>
}
export default App;
组件实战开发
日历的实现
核心功能
-
日历组件:显示月视图日历,支持日期选择
-
月份导航:通过左右箭头按钮切换上/下个月
-
日期选择:点击日期可选中并触发回调
-
状态管理:
- 使用
useState
管理当前显示的日期 - 通过 props 接收默认值 (
defaultValue
) - 日期变化时通过
onChange
回调通知父组件
- 使用
js
import { useState } from "react";
import "./index.css";
function Calendar(props) {
const {defaultValue,onChange}=props
// 对象的解构
const [date, setDate]=useState(defaultValue);
const handlePrevMonth=()=>{
setDate(new Date(date.getFullYear(),date.getMonth()-1,1))
}
const handleNextMonth=()=>{
setDate(new Date(date.getFullYear(),date.getMonth()+1,1))
}
const daysOfMonth=(year,month)=>{
return new Date(year,month+ 1,0).getDate();
}
const firstDayOfMonth=(year,month)=>{
return new Date(year,month,1).getDay();
}
// getDate()方法参数6,0 日期是从1号开始的,如果写0会自动变成一个月的最后一天
// getDay()得到的是星期几
const renderDates=()=>{
const days=[]
const daysCount=daysOfMonth(date.getFullYear(),date.getMonth())
const firstDay=firstDayOfMonth(date.getFullYear(),date.getMonth())
for(let i=0;i<firstDay;i++){
days.push(<div key={`empty--${i}`} className="empty"></div>)
}
for(let i=1;i<=daysCount;i++){
const clickHandler=()=>{
const curDate=new Date(date.getFullYear(),date.getMonth(),i)
setDate(curDate)
// 对每个日期添加点击函数
onChange(curDate)
}
if(i===date.getDate()){
days.push(<div key={i} className="day selected" onClick={()=>{clickHandler()}}>{i}</div>)
}
else{days.push(<div key={i} className="day" onClick={()=>{clickHandler()}}>{i}</div>)}
}
return days
}
return (
<div className="calendar">
<div className="header">
<button onClick={handlePrevMonth}><</button>
<div>{date.getFullYear()}年{date.getMonth()+1}月</div>
<button onClick={handleNextMonth}>></button>
</div>
<div className="days">
<div className="day">日</div>
<div className="day">一</div>
<div className="day">二</div>
<div className="day">三</div>
<div className="day">四</div>
<div className="day">五</div>
<div className="day">六</div>
{renderDates()}
</div>
</div>
)
}
export default Calendar;
-
月份处理:
- JavaScript 的
Date
对象中月份从 0 开始(0=1月) - 显示时需
month + 1
,操作时需注意原始值
- JavaScript 的
-
日期选择逻辑:
- 点击日期时创建新的日期对象:
new Date(year, month, day)
- 同时更新组件状态和触发父组件回调
- 点击日期时创建新的日期对象:
js
.calendar {
width: 300px;
height: 250px;
border: 1px solid black;
padding: 10px;
}
.header {
display: flex;
justify-content: space-between;
align-items: center;
height: 40px;
}
.days {
display: flex;
flex-wrap: wrap;
}
.empty,.day {
width: calc(100% / 7);
height: 30px;
text-align: center;
}
.day:hover,.selected{
background-color: #ccc;
cursor: pointer;
}
js
import Calendar from "./calendar";
//通过defaultValue来传入初始值date
// 修改date之后可以在onChange里拿到最新的值 ---非受控
function App(){
return(
<Calendar defaultValue={new Date(2025,6,8)} onChange={(newDate) => { alert(newDate.toLocaleDateString())
//父传子
}} />
)
}
export default App
快来试试写一个日历吧