React基础教程
目录:
- react背景介绍和优势
- 如何看一个网站是用vue、react、jquery 开发
- 数据驱动视图
- jsx语法
- 常用React写法
- React内置组件
- React生命周期
- useState和immer
- React常用hooks
- React自定义Hook 函数方法
- 组件传值
- React 第三方hook推荐
中文文档
全球最火爆的前端框架
-
全球范围最火的前端框架 (国内 Vue 最火爆)
-
国内大厂很多用 React
-
React在npmjs下载量 (平均每周下载量2000w+)
对比其他框架
- Angular - 老牌框架,国内已不用 ----- 早期 vue模仿对象 ----
- Vue3 - 国内常用,和 React 越发趋同(Composition API 和 JSX)
- Svelte ,Solid.js - 后起之秀,国内尚未形成规模
生态和库
-
状态管理:useState/useReducer(useContext(用于小的全局状态管理), Redux , Zustand
-
Vite 用于客户端 React 应用 (比wepack打包速度) SWC是rust编写的全新编译器
-
风格和格式 - eslint prettier husky(git hooks) 等
-
Utility-First-CSS- tailwindcss 95%的义务只需写HTML, 无须写css
-
UI组件库 Ant Design(国内最推荐) Material UI(最受欢迎)Mantine UI(最推荐)Chakra UI (最推荐)Radix (无样式设计系统)
-
CSS-in-JS:Styled Components(最受欢迎)、Emotion(备选方案)
-
移动应用:ReactNative
-
服务器端渲染的 React 应用: Next.js 实现服务器端渲染(SSR)和客户端渲染(CSR)之间的无缝切换
-
桌面应用:Electron tauri
-
国际化: React-i18next FormatJS 如果用一些UI库仅UI内部字段支持国际化 (例如:antd)
-
部署和托管 Netlify 或 Vercel(尤其是 Next.js)是流行的解决方案
-
单元/集成测试:Jest/Vitest + React Testing Library(最受欢迎)
-
VR/AR react-three-fiber react-360 aframe-react
React在跨境电商前端中的重要性 90%以上主流跨境电商使用React开发---其他vue开发---jquery几乎没有
如何看一个网站是用vue、react、jquery 开发
查看方式
1.简单的方式就是直接打开控制台查看(有很大误差,有些网站大部分网站自定义id)
2.在Chrome 应用商店查找
(1)vue开发工具:vue.js devtools
(2)react开发工具:React Developer Tools
3.固定拓展角标,检测当前网站检测到 拓展角标 自动会亮
vue.js、react.js、vue.js+nuxt.js ....
案例网站和技术栈:
https://chatgpt.com/ react+next+tailwindcss
https://www.tiktok.com/ react+next
https://www.walmart.com/ react+next
https://www.temu.com/ react
https://www.ozon.ru/ vue3+nuxt
https://us.shein.com/ 前台vue 后台react
背景介绍
- React 是一个用于构建用户界面的 JAVASCRIPT 库。
- React 主要用于构建 UI,很多人认为 React 是 MVC 中的 V(视图)(控制器视图),但并不是。
- React 起源于 Facebook 的内部项目,用来架构 Instagram 的网站,并于 2013 年 5 月开源。
- React 以其高性能和简洁的代码逻辑著称,因此吸引了越来越多的开发者关注和使用。
语法
-
Vue 定义了 template 语法,如
v-if等,而 JSX 更多依赖于 JS 语法。 -
所以,JS 语法熟练的基础上,React 更简单
数据驱动视图
公式 UI = f(state) (开始可能不理解)
例如,要增加一个 todo item (修改、删除,用 DOM 操作更麻烦)
<ul>
<li>吃饭</li>
<li>睡觉</li>
</ul>
// 用 jQuery 的代码逻辑 - DOM 操作
const $li = $('<li>new todo</li>')
$ul.append($li)
用 React
<ul>
{ list.map(todo => <li>{todo}</li>) }
</ul>
// 用 React 的代码逻辑 - JS 操作
const [list, setList]= useState(['吃饭', '睡觉'])
setList(val => val.concat('new todo'))
数据驱动视图的好处
- 只关注业务数据,解放 DOM 操作,提高开发效率
- 适合大型复杂的前端项目 (否则,光 DOM 操作也受不了)
开始Demo
创建 React SPA单页面 项目
SPA优点 页面内容在单个 HTML 页面中动态加载和更新,而不是每次导航时重新加载整个页面
- 快速响应:页面不需要完全刷新,用户体验更流畅。
- 减少服务器负担:只在首次加载时请求页面框架,之后只请求数据。
- 更好的用户体验:能够实现动态和交互丰富的用户界面。
- 客户端渲染:页面逻辑和渲染都在客户端完成,减少服务器压力。
react三种文件后缀名
- js 、jsx 、 tsx
vue中也可以支持 jsx 、 tsx写法
npm init vite 包名 // 创建vue项目自己定义模版一定要选择 Customize with create-vue
vscode插件推荐(js/ts)
Simple React SnippetsTypescript React code snippets
创建方式很多种(参考一下几种方式)
- vite创建React项目 VRA (个人强烈推荐) Typescript推荐SWC技术支持:SWC是一个基于Rust编写的JavaScript和TypeScript转换器,相比传统的Babel转换,单线程下其性能提高了约20倍 ,多线程下快70倍
npm create vite@latest
- 传统创建建方式 CRA (npmjs上核心包)(官方推荐 使用最多的脚手架 使用 webpack 作为打包工具)
! npx -----> Nodejs 工具命令,查找并执行后续的包命令
npx create-react-app react-cra --template typescript (模版名)
- 创建(服务端SSR渲染SEO框架) create-next-app
npx create-next-app@latest --template typescript (模版名)
eject 命令存在的意义就是更改 webpack 配置
web-vitals 性能优化用
安装基础包
npm install antd --save
npm install @ant-design/icons --save
npm install classnames
npm install immer --save
jsx语法
jsx是js语法的拓展,浏览器本身不能识别,需要通过解析工具做解析之后才能在浏览器上运行,(babel , SWC)
Babel是一个广泛使用的转码器,是前端工程化中重要的部分,可以将ES6代码转为ES5代码,从而在现有环境执行。
SWC 是一个基于 Rust 的可扩展平台,适用于下一代快速开发工具。 它被 Next.js、Parcel 和 Deno 等工具以及 Vercel、字节跳动、腾讯、Shopify 等公司使用
// 编译前 Babel
input.map(item => item += 1);
// 编译后 ES5++ 以上写法会转为 ES5写法
input.map(function (item) {
return item += 1;
});
// react中 SWC
<div>hello word!</div>
|--> babel--->
|--->浏览器控制台Sources中查看
jsx语法---综合案例
一个案例讲完jsx
import React, { useState, Fragment } from "react";
import { Button, Input } from "antd";
import "./style.css";
// import DynamicallyChange from "./view/DynamicallyChange";
// import TestClass from "./view/TestClass";
// import SuspenseComponent from "./components/SuspenseComponent";
// import SimpleLifecycleClass from "./view/SimpleLifecycleClass";
// import SimpleLifecycleFunc from "./view/SimpleLifecycleFunc";
// import TestImmerState from "./view/TestImmerState";
// import EffectFuncHooks from "./view/EffectFuncHooks";
// import IconSwitcher from "./view/IconSwitcher";
// import SquareRootCalculator from "./view/SquareRootCalculator";
// import TestUseId from "./view/TestUseId";
function App() {
const [count, setCount] = useState(0);
const [isFlag, setIsFlag] = useState(false);
const userList = [
{ id: 1, name: "John" },
{ id: 2, name: "Jane" },
{ id: 3, name: "Bob" },
];
// 自定义组件首字母大写
const ChildComponent = () => {
return <h5>首字母大写子组件</h5>;
};
// 内联样式 --- 对象写法
const styles = {
context: {
backgroundColor: "lightblue",
fontSize: "16px",
color: "white",
},
text: {
backgroundColor: "darkblue",
fontSize: "16px",
marginBottom: "10px",
color: "white",
},
};
// 四个子组件动态切换
const Test1 = () => {
return <h5 style={{ color: "red",fontSize:"20px"}}>Test1</h5>;
};
const Test2 = () => {
return <h5 style={{ color: "orange" ,fontSize:"20px"}}>Test2</h5>;
};
const Test3 = () => {
return <h5 style={{ color: "green",fontSize:"20px"}}>Test3</h5>;
};
const Test4 = () => {
return <h5 style={{ color: "blue" ,fontSize:"20px"}}>Test4</h5>;
};
const components = {
1: <Test1 />,
2: <Test2 />,
3: <Test3 />,
4: <Test4 />
};
const TestComponentUp = ({ props }) => components[props] || null;
return (
<>
{/* 标签必须闭合 img br hr .... */}
{/* <hr> */}
<h2>APP</h2>
{/* Fragment 标签好处是不浪费多余的标签,代码更加简洁 */}
{/* {userList.map((val) => {
return (
<Fragment key={val.id}>
<p>{val.name}</p>
</Fragment>
);
})} */}
{/* 组件首字母大写 */}
{/* <ChildComponent /> */}
{/* className 案例 */}
<p className="appSpan">这是一个测试className标签</p>
{/* style 内联样式标签上面写法 案例 --- key大写 --- */}
<p style={{ color: "red", fontSize: "20px", backgroundColor: "yellow" }}>
这是一个测试---内部---style标签
</p>
{/* style 内联样式外部写法 案例 --- key大写 --- */}
<p style={styles.context}>这是一个测试---外部---context标签</p>
<div style={styles.text}>这是一个测试---外部---text标签</div>
{/* css in js 写法--- 略 */}
{/* label标签for改htmlFor */}
<label htmlFor="name">Name:</label>
<input id="name" type="text" />
{/* 事件案例 onClick onChange */}
<div>
<Button
type="primary"
style={{ marginTop: "20px" }}
onClick={() => setCount(count + 1)}
>
count is: {count}
</Button>
<Input
placeholder="请输入...."
onChange={(e) => console.log("99++++++", e.target.value)}
/>
</div>
{/* 以下是 ------> JS 表达式 案例 */}
{/* 条件渲染 三元表达式案例 */}
{count > 3 ? (
<p style={{ color: "green", fontSize: "20px" }}>count大于0</p>
) : (
<p style={{ color: "red", fontSize: "20px" }}>count小于0</p>
)}
{/* 多个变量或组件条件渲染时 */}
<TestComponentUp props={count} />
{/* 列表点击谁谁变色 案例 动态绑定class方法 */}
{/* <DynamicallyChange /> */}
{/* <TestClass /> */}
{/* 组件显示隐藏生命周期 */}
{/* <Button type="primary" onClick={() => setIsFlag(!isFlag)}>
{isFlag ? "隐藏" : "显示"}
</Button>
{isFlag && <TestClass />} */}
{/* 内置组件Suspense 案例 */}
{/* <SuspenseComponent/> */}
{/* immer 案例 */}
{/* <TestImmerState/> */}
{/* 类组件生命周期 */}
{/* <SimpleLifecycleClass/> */}
{/* 函数组件生命周期 */}
{/* <SimpleLifecycleFunc/> */}
{/* useEffect 副作用hooks案例 */}
{/* <EffectFuncHooks/> */}
{/* useTransition 案例 */}
{/* <IconSwitcher/> */}
{/* useMemo 案例 */}
{/* <SquareRootCalculator/> */}
{/* useId 案例 */}
{/* <TestUseId/> */}
</>
);
}
export default App;
style.css
.appSpan{
font-size: 20px;
font-weight: bold;
color: #008080;
}
.appSpan:hover{
color: red;
text-decoration: underline;
cursor: pointer;
}
ul{
list-style-type: none;
padding: 0;
margin: 0;
}
li{
margin: 0;
padding: 0;
font-size: 20px;
font-weight: bold;
color: #008080;
margin-top: 10px;
}
.userClass1{
color:rgb(3, 155, 229);
}
.active{
color: red;
text-decoration: underline;
cursor: pointer;
}
标签:
1.每一段 JSX 只能有一个根节点,或者使用 `<></>` `<React.Fragment></React.Fragment>` `<Fragment></Fragment>`
使用显式 <React.Fragment> 语法声明的片段可能具有 key 而< key={item.id}></> 会报错
----------- 同于Vue <template></template>
2.自定义组件首字母大写
3.{} 可以在 JSX 中使用花括号来嵌入动态内容、变量、函数调用等。 ---- vue中插值表达式{{}}
4.标签必须闭合状态 eg: img br hr
属性 :
CSS-in-JS:Styled Components(最受欢迎)
- CSS-in-js 并不是内联 style (重要!!!),它会经过工具的编译处理,生成 CSS class 的形式
! 和 HTML 属性基本一样,但有些和 JS 关键字冲突了
class 要改为 className
style 要写成JS对象(不能是string),key采用驼峰写法
4种形式写法:(1.引入css,2.标签style直接写 3.className,4.css in js模式
! label中 for 要改为 htmlFor
<label htmlFor="Name">Name:</label>
<input id="name" type="text" />
! // css 对象写法
const styles = {container: {},text: {},};
const MyComponent = () => (
<div style={styles.container}>
<p style={styles.text}>This is a styled paragraph.</p>
</div>
);
事件
1.onClick ={()=>{console.log('9999')}}
2.onClick ={handleClick}
3.onChange ....
参数传递:onClick={(e)=>handleClick(e,userList)}
JS 表达式 ( 判断 循环....)
运算符: 三元 ....
{flag && <p>hello</p>}
{flag ? <p>你好</p> : <p>再见</p>}
多条件:多个变量或组件条件渲染时 { handletest5(item.id)
const handletest5 = (val) => {
if(val === 1){
return <Test />
}else{
return <Test2 />
....
}
}
! vue2/vue3中使用组件component-----> <component :is="currentComponent" ></component>
循环:{ XXX.map((item)=> return ....)}
动态绑定:
1.className className={`userClass1 ${state.Type === item.id && 'active'}`}
2.classNames库
! 注意:同级别 `key` 必须唯一,尽量不用 index,`key` 用于优化 VDOM diff 算法 diff(oldNode, newNode)....
动态绑定class之 classNames npmjs 地址 (千万周下载量)
npm install classnames --save // 安装classnames
DynamicallyChange.jsx // 案例
// 传统写法
className={`userClass1 ${selectedUserId === item.id ? 'active' : ''}`}
// classNames 写法
className={classNames('userClass1', { 'active': selectedUserId === item.id })}
import React, { useState } from 'react';
// import '../style.css';
import classNames from 'classnames';
const DynamicallyChange = () => {
const [selectedUserId, setSelectedUserId] = useState(null);
// 点击动态绑定类名
const handleUserClick = (id) => {
setSelectedUserId(id);
};
const userList = [
{ id: 1, name: 'John' },
{ id: 2, name: 'Jane' },
{ id: 3, name: 'Doe' },
{ id: 4, name: 'Smith' },
];
return (
<>
<ul>
{/* 一般动态绑定class方式 */}
{userList.map((item) => (
<li
key={item.id}
onClick={() => handleUserClick(item.id)}
className={`userClass1 ${selectedUserId === item.id ? 'active' : ''}`}
>
<span>{item.id}</span>: <span>{item.name}</span>
</li>
))}
</ul>
</>
);
};
// className方式
// className={classNames('userClass1', { 'active': selectedUserId === item.id })}
export default DynamicallyChange;
常用React写法
// 类组件(Class Components)
TestClass.jsx
import React, { Component } from 'react'
export default class ParentComponent extends Component {
render() {
return (
<div>ParentComponent</div>
)
}
}
// 函数组件(Function Components)
rsf TestFunc.jsx
import React from 'react';
function ParentComponent(props) {
return (
<div>
</div>
);
}
export default ParentComponent;
// 无状态组件(Stateless Components)
const MyComponent = (props) => {
return (<div>{props.message}</div>);
};
// Hooks 的函数组件 模拟请求 (常用的)
rfuc TestHooks.jsx
import React, { useState, useEffect } from 'react';
const ExampleComponent = () => {
const [count, setCount] = useState(0);
const [data, setData] = useState(null);
useEffect(() => {
// 挂载(componentDidMount)时执行的逻辑
console.log('组件挂载----', count);
// 模拟数据获取
fetchData();
// 卸载(componentWillUnmount)时执行的逻辑
return () => {
console.log('组件卸载执行----', count);
};
}, []); // 空数组作为依赖项,确保只在挂载和卸载时执行
// useEffect Hook 用于在 count 变化时执行副作用
useEffect(() => {
console.log('组件更新:', count);
// 可以在这里处理 count 变化的副作用,如更新文档标题
document.title = `Count: count`;
}, [count]); // 仅在 count 变化时执行
// 模拟数据获取函数
const fetchData = async () => {
// 假设从 API 获取数据
const response = await fetch('http://localhost:4000/api/users?pageNum=1&pageSize=5');
const result = await response.json();
setData(result);
};
// 处理按钮点击事件的函数
const handleClick = () => {
setCount(prevCount => prevCount + 1);
};
console.log('执行render函数-----', count);
return (
<div>
<h1>ExampleComponent</h1>
<p>Count: {count}</p>
<button onClick={handleClick}>Increment</button>
<div>
{data ? (
<pre>{JSON.stringify(data, null, 2)}</pre>
) : (
<p>Loading data...</p>
)}
</div>
</div>
);
};
export default ExampleComponent;
React内置组件
Fragment 上面讲过--- 略....
Profiler 渲染时间记录函数(编程式测量渲染性能 )
const onRenderCallback = (
id, // Profiler树的id,指定的id属性的值
phase, // 当前渲染阶段:'mount'(挂载) 或 'update'(更新)
actualDuration, // 本次更新的实际渲染时间
baseDuration, // 最理想情况下的渲染时间
startTime, // 本次渲染开始的时间
commitTime, // 本次渲染结束的时间
interactions // 属于本次更新的交互
) => {
console.log(`id: ${id}`);
console.log(`phase: ${phase}`);
console.log(`actualDuration: ${actualDuration}`);
console.log(`baseDuration: ${baseDuration}`);
console.log(`startTime: ${startTime}`);
console.log(`commitTime: ${commitTime}`);
console.log(`interactions:`, interactions);
const renderDuration = commitTime - startTime;
console.log("渲染这个组件所用的时间为", renderDuration, "毫秒");
};
<Profiler id="SimpleLifecycle" onRender={onRenderCallback}><组件/></Profiler> // 包裹组件调优
StrictMode 启用严格模式 !在开发环境会执行两次render函数
Suspense 允许在子组件完成加载前展示后备方案 (通常是----Loading组件、图片预加载组件、骨架屏组件)
// 案例片段
import React, { useState, Suspense } from 'react';
import { Button } from 'antd';
// Loading 组件
const Loading = () => <h2>Loading...</h2>;
// 自定义 fetch 包装器
let data;
let promise;
// 异步加载数据
const fetchData = () => {
if (!promise) {
promise = fetch('http://localhost:4000/api/users?pageNum=2&pageSize=2')
.then((res) => res.json())
.then((result) => {
data = result;
return data;
});
}
if (data) {
return data;
}
throw promise;
};
// 数据展示 组件
const DataDisplay = () => {
const data = fetchData();
return <pre>${JSON.stringify(data, null, 2)}</pre>;
};
// Suspense 案例组件
const SuspenseComponent = () => {
const [showData, setShowData] = useState(false);
// 点击按钮发出请求
const getHandleUserData = () => setShowData(true);
return (
<>
<Button type='primary' onClick={getHandleUserData}>发出请求</Button>
{showData && (
<Suspense fallback={<Loading />}>
<DataDisplay />
</Suspense>
)}
</>
);
};
export default SuspenseComponent;
React生命周期
类组件:
import React, { Component } from 'react';
class SimpleLifecycleClass extends Component {
// 类组件的一个特殊方法,用于在组件实例化时初始化状态和绑定事件处理程序
constructor(props) {
super(props); // 调用父类的构造函数component 便于访问this.props,以便正确初始化组件实例
this.state = { count: 0 };
console.log('01构造函数---constructor');
}
// 组件将要挂载时候触发的生命周期函数
componentWillMount() {
console.log('02组件将要挂载---componentWillMount');
}
// 组件挂载完成时候触发的生命周期函数
componentDidMount() {
console.log('04组件将要挂载---componentDidMount');
}
// 是否要更新数据,如果返回 true 才会更新数据 (控制组件是否应该更新。通过对比当前的 props 和 state 与新的 props 和 state 来决定是否重新渲染组件)
shouldComponentUpdate(nextProps, nextState) {
console.log('是否要更新数据');
console.log(nextProps); // 父组件传给子组件的值,这里没有会显示空
console.log(nextState); // 数据更新后的值
return true; // 返回 true,确认更新
}
// 静态方法--getDerivedStateFromProps---在组件实例化时和接收到新属性时调用。它用于更新 state 以响应 prop 的变化
static getDerivedStateFromProps(nextProps, prevState) {
console.log('prop changed------getDerivedStateFromProps', nextProps, prevState);
// 根据 props 更新 state
// 返回 null 表示不更新 state
return null;
}
// 你在父组件里面改变 props 传值的时候触发的函数
componentWillReceiveProps() {
console.log('父子组件传值,父组件里面改变了props的值触发的方法');
}
// 将要更新数据的时候触发的
componentWillUpdate() {
console.log('update-------------组件将要更新');
}
// 更新数据时候触发的生命周期函数
componentDidUpdate() {
console.log('update-------------组件更新完成');
}
// 组件将要销毁的时候触发的生命周期函数,用在组件销毁的时候执行操作
componentWillUnmount() {
console.log('componentWillUnmount');
}
handleClick = () => {
this.setState({ count: this.state.count + 1 });
};
render() {
console.log('render函数---render');
return (
<>
<h1>SimpleLifecycleClass Demo</h1>
<p>Count: {this.state.count}</p>
<button onClick={this.handleClick}>Increment</button>
</>
);
}
}
export default SimpleLifecycleClass;
函数组件:
// 父组件控制组件挂载/卸载
const [showCounter, setShowCounter] = useState(true);
const toggleCounter = () => {
setShowCounter(prevShowCounter => !prevShowCounter);
};
<Button type="primary" onClick={toggleCounter}>isShow</Button>
{showCounter&&
<SimpleLifecycle/>}
import React, { useState, useEffect } from 'react';
const SimpleLifecycleFunc = () => {
const [count, setCount] = useState(0);
useEffect(() => {
// 挂载(componentDidMount):通过传递一个空数组作为 useEffect 的依赖数组
console.log('componentDidMount');
// 更新页面标题
document.title = `Count: count`;
// 设置一个定时器
const timer = setInterval(() => console.log('tick'), 1000);
// 卸载(componentWillUnmount):通过返回一个清理函数来模拟
return () => {
console.log('componentWillUnmount');
// 清理定时器
clearInterval(timer);
};
}, [count]); // 依赖项数组包含 count
useEffect(() => {
// 更新(componentDidUpdate):每次组件更新后执行 useEffect。第一次渲染也会执行
document.title = `Count: count`; console.log('componentDidUpdate');
}); // 没有依赖数组,每次渲染都会执行(每次执行render都会执行) 空数组作为依赖项,只有在组件挂载和卸载时执行
const handleClick = () => {
setCount(count + 1);
};
console.log('render----函数式组件渲染');
return (
<div>
<h1>Simple Lifecycle Demo</h1>
<p>Count: {count}</p>
<button onClick={handleClick}>Increment</button>
</div>
);
};
export default SimpleLifecycleFunc;
react render函数在开发环境渲染两次问题描述
在开发模式下,即使没有使用 React.StrictMode,React 仍然可能在某些情况下多次调用组件的渲染和生命周期钩子函数。这种行为在 React 18 中尤其明显,因为 React 18 在开发模式下会引入额外的检查和重渲染,以帮助开发者发现潜在的问题。
具体到你的代码,componentDidUpdate 打印两次的原因主要有两个方面:
- 双重调用:在开发模式下,React 18 会为了帮助开发者发现副作用的问题,故意调用某些生命周期钩子两次。这个行为在生产模式下是不会发生的。
- 依赖数组问题:你的第一个
useEffect钩子依赖于count,因此每次count变化时,该useEffect钩子都会重新执行并触发componentDidMount和componentWillUnmount的模拟。同时,因为没有依赖数组的第二个useEffect钩子会在每次渲染后执行,所以它也会触发componentDidUpdate。
为了确认是否是开发模式导致的,你可以构建一个生产版本并查看结果是否相同。
// 创建生产构建:
npm run build
// 启动生产服务器:``
npm install -g serve
serve -s build
React内置Hook 函数
(常用的) 针对函数组件 ,类组件没有
列举:
-
useState 、useEffect 、useTransition 、useRef 、useMemo、useId、useContext、useLayoutEffect、useReducer
React 从 16开始,全面推广函数式组件和 Hooks ,随后 Vue3 也开始参考这种形式。
useState
useState 管理状态的hook(两种方式表达,统一管理/单一管理) ------ vue ----- data
useState和普通JS变量不同的是,状态变量一旦发生变化, 组件的视图UI也会跟着变化(数据驱动视图/数据驱动UI更新)
- 单一管理 ------ vue3--- Components
1.const [isShow, setIsShow] = useState(false); setIsShow(!isShow);
- 统一管理 (多个状态) ------ vue--- Options
const [state, setState] = useState({
isShow: false,
flag: true,
count:1,
});
setState((prevState) => ({ // 修改状态
...prevState, // 保持其他状态不变
isShow: !prevState.isShow,
}));
render中: {{state.count}}
!不能直接修改,eg: state.count ++ ,数据变,视图不会更新
模拟vue表单双向绑定:(表单受控绑定) --- vue --- v-model --- 效果
import React, { useState, useMemo } from 'react';
import { Input } from 'antd';
const TestVModel = () => {
const [value, setValue] = useState('');
return (
<div style={{ padding: '10px' }}>
<Input
type="text"
value={value}
onChange={(e) => setValue(e.target.value)}
placeholder="输入内容...."
style={{ width: '250px', marginBottom: '10px' }}
/>
<p>内容: {value}</p>
</div>
);
};
export default TestVModel;
Immer 简化了不可变数据结构的处理 immer文档地址
npm install immer --save // 安装依赖immer
// useState和immer修改state对比案例
TestImmerState.jsx
import React, { useState } from 'react';
import { Button } from 'antd';
import { produce } from 'immer';
const TestImmerState = () => {
const [state, setState] = useState({
isShow: false,
flag: true,
count: 1,
useList: [
{
id: 1,
name: '张三',
},
{
id: 2,
name: '李四',
},
{
id: 3,
name: '王五',
},
],
});
// 修改简单的还好 一个...扩展运算符
const handleClick = () => {
setState((prevState) => ({
// 扩展 prevState 对象中的所有属性
...prevState,
// 更新 count 属性
count: prevState.count + 1,
}));
};
// 使用 immer 点击修改状态
// 修改复杂的 --- 把 name: "XXX",改成 name: "Tom"
const handleClickNamed = (id) => {
setState(
// 使用 produce 方法创建一个新的状态, 该函数会接收到一个 draft 对象
// draft 对象是当前状态的一个代理, 你可以像修改可变对象那样修改它
produce((draft) => {
const user = draft.useList.find((val) => val.id === id);
if (user) {
user.name = 'Tom';
}
})
);
};
return (
<>
<Button onClick={handleClick}>Increment</Button>
<p>{state.count}</p>
<>
{state.useList.map((val) => {
return (
<div key={val.id}>
<p onClick={() => handleClickNamed(val.id)}>{val.name}</p>
</div>
);
})}
</>
</>
);
};
export default TestImmerState;
useEffect 副作用hooks (类似 vue3中-----------------watchEffect)
// 案例
EffectFuncHooks.jsx
import React, { useState, useEffect } from 'react';
const SimpleLifecycleFunc = () => {
const [count, setCount] = useState(0);
useEffect(() => {
// 挂载(componentDidMount):通过传递一个空数组作为 useEffect 的依赖数组
console.log('componentDidMount');
// 更新页面标题
document.title = `Count: count`;
// 设置一个定时器
const timer = setInterval(() => console.log('tick'), 1000);
// 卸载(componentWillUnmount):通过返回一个清理函数来模拟
return () => {
console.log('componentWillUnmount');
// 清理定时器
clearInterval(timer);
};
}, [count]); // 依赖项数组包含 count
useEffect(() => {
// 更新(componentDidUpdate):每次组件更新后执行 useEffect。第一次渲染也会执行
document.title = `Count: count`; console.log('componentDidUpdate');
}); // 没有依赖数组,每次渲染都会执行(每次执行render都会执行) 空数组作为依赖项,只有在组件挂载和卸载时执行
const handleClick = () => {
setCount(count + 1);
};
console.log('render----函数式组件渲染');
return (
<div>
<h1>Simple Lifecycle Demo</h1>
<p>Count: {count}</p>
<button onClick={handleClick}>Increment</button>
</div>
);
};
export default SimpleLifecycleFunc;
useLayoutEffect为有DOM操作的副作用hooks
useTransition (用于管理过渡transition状态,以便在状态更新时优化用户体验)
// IconSwitcher.jsx
import React, { useState, useTransition } from 'react';
import { Button, Space } from 'antd';
import { SmileOutlined, FrownOutlined } from '@ant-design/icons';
const IconSwitcher = () => {
const [isHappy, setIsHappy] = useState(true);
// 2000 代表过渡的最大持续时间为 2000 毫秒(2 秒)。如果过渡时间超过这个值,React 会优先处理高优先级的更新操作,并可能取消较低优先级的操作。
const [isPending, startTransition] = useTransition(2000);
const toggleIcon = () => {
// 将状态更新标记为过渡更新
startTransition(() => {
setIsHappy((prevIsHappy) => !prevIsHappy);
});
};
return (
<div style={{ textAlign: 'center', marginTop: 50 }}>
<Space direction="vertical" size="large">
{/* isPending 表示是否有待处理的过渡更新 */}
<Button type="primary" onClick={toggleIcon} loading={isPending}>
切换图标
</Button>
<div style={{ fontSize: 48 }}>
{isHappy ? <SmileOutlined /> : <FrownOutlined />}
</div>
</Space>
</div>
);
};
export default IconSwitcher;
useRef (可以用来直接访问 DOM 元素。用来保持不引起重新渲染的可变值) -----vue2 refs.XXX ------ vue3中 ref()
import React, { useRef } from 'react';
const FocusInput = () => {
// 创建一个 ref 对象
const inputRef = useRef(null);
const focusInput = () => {
// 使用 ref 对象访问 DOM 元素并使其获得焦点
inputRef.current.focus();
};
return (
<div>
<input ref={inputRef} type="text" placeholder="点击按钮获取焦点" />
<button onClick={focusInput}>Focus the input</button>
{/* 当点击按钮时,input 元素将获得焦点 */}
</div>
);
};
export default FocusInput;
// vue3用法 eg:轮播图手动执行下一张操作
<el-carousel indicator-position="none" :height="carouselHeight" ref="slideCarousel" :autoplay="autoplayTa"/>
import { ref } from "vue";
const slideCarousel = ref();
slideCarousel.value.next();
useMemo 用于处理
// SquareRootCalculator.jsx
import React, { useState, useMemo } from 'react';
import { Input } from 'antd';
const SquareRootCalculator = () => {
const [number, setNumber] = useState(0);
// 使用 useMemo 缓存平方根的计算结果
const squareRoot = useMemo(() => {
console.log('计算平方根....');
return Math.sqrt(number);
}, [number]);
return (
<div style={{ padding: '20px' }}>
<Input
type="number"
value={number}
onChange={(e) => setNumber(e.target.value)}
placeholder="输入数字...."
style={{ width: '200px', marginBottom: '20px' }}
/>
<p>平方根: {squareRoot}</p>
</div>
);
};
export default SquareRootCalculator;
useId 生成唯一ID
- 保证 ID 的唯一性: 适用于服务端渲染(SSR)
- 更简洁的代码: 使用
useId可以简化代码,不需要手动管理和生成 ID
import React, { useId } from 'react';
function TestUseId() {
const inputId = useId(); // 生成唯一 ID
return (
<form onSubmit={(e) => e.preventDefault()}>
<label htmlFor={inputId}>姓名:</label>
<input id={inputId} type="text" name="name" />
</form>
);
}
export default TestUseId;
useReducer 通常用于封装逻辑复杂的自定义hooks -------- 略
React自定义Hook 函数
----- (举个栗子)
/hooks/useReCode.jsx 读取url任何键值hooks
import { useEffect, useState } from 'react';
const useReCode = (paramKey) => {
const [recode, setRecode] = useState(null);
useEffect(() => {
const url = window.location.href;
const searchParams = new URLSearchParams(new URL(url).search);
const recodeValue = searchParams.get(paramKey);
setRecode(recodeValue);
}, [paramKey]);
return [recode, setRecode];
};
export default useReCode;
// 使用
// ?recode=12345&ffff=00000
import useReCode from "./hooks/useReCode";
// 事件调用
const [paramKey, setParamKey] = useState('recode'); // 默认属性可改为空
const [recode, setRecode] = useReCode(paramKey);
const changeParamKey = () => {
// 假设按钮点击时更改参数键值
setParamKey('ffff');};
<Button type="primary" onClick={changeParamKey}>Change Param Key</Button>
<p>{recode}</p>
- 有些自定义hooks类似在vue2中vue内置过滤器filters,在渲染list时,每一项都会执行后面的方法,vue3之后全面hooks化。
组件传值(常用方法)
- 通过 props 传递数据(父组件传递数据给子组件)
- 通过回调函数传递数据(子组件传递数据给父组件)
- 使用 Context API(适用于跨越多个组件层级的数据共享 useContext)
- 使用状态管理库(如 Redux 或 Zustand)
父传子 ------ 案例
import React, { useState } from "react";
// 子组件
const ChildComponent = ({value}) => {
return <div>{value}</div>;
};
// 通过 props 传递数据(props是一种用于在组件树中传递数据的机制)
// 父组件
const ParentComponent = () => {
const [message, setMessage] = useState("Hello from Parent Component!");
return (
<div>
<h1>父组件</h1>
<ChildComponent value={message} />
</div>
);
};
export default ParentComponent;
子传父 ------ 案例
import React, { useState } from "react";
// 子组件
const ChildComponent = ({ onMessageChange }) => {
const handleChange = (event) => {
onMessageChange(event.target.value);
};
return (
<div>
<input type="text" onChange={handleChange} placeholder="请输入...." />
</div>
);
};
// 通过事件携带参数的形式传递props(属性)
// 父组件
const ParentComponent = () => {
const [message, setMessage] = useState("");
return (
<div>
<h1>父组件</h1>
<p>子组件传递的值: {message}</p>
<ChildComponent onMessageChange={setMessage} />
</div>
);
};
export default ParentComponent;
useContext 在组件树中访问 Context 对象的值。Context 使得你能够在组件树中共享数据,而不需要通过 props 逐层传递。 useContext 允许你在函数组件中轻松地访问 Context 提供的值。
!不管组件包裹多少层,都能通过Context 传递
import React, { createContext, useState, useContext } from "react";
// 创建 Context 这个对象包含了 Provider 和 Consumer 组件,用于在组件树中共享数据。
const MessageContext = createContext();
// 内层组件
const ChildComponent = () => {
const { message, setMessage } = useContext(MessageContext);
// 从内层组件修改外层组件的状态
const handleChangeMessage = () => {
setMessage("来自内层组件的新消息!");
};
return (
<div>
<div>{message}</div>
<button onClick={handleChangeMessage}>内层点击更新消息</button>
</div>
);
};
// 中层组件
const MiddleComponent = () => {
return (
<>
<div>
中层组件
<ChildComponent />
</div>
</>
);
};
// 外层组件
const TextUseContextProvider = () => {
const [message, setMessage] = useState("来外层组件的信息!");
return (
<>
<MessageContext.Provider value={{ message, setMessage }}>
<div>
<h4>外层组件</h4>
<MiddleComponent />
</div>
</MessageContext.Provider>
</>
);
};
export default TextUseContextProvider;
Provider的作用
Provider 组件的作用是提供 Context 的值,这个值会被其子组件(包括子孙组件)访问到。通常在树的高层次使用 Provider 来设置 Context 的值,并让下层组件能够访问和更新这些值。
Consumer的作用
Consumer 组件则用于在组件树中消费 Context 的值。它需要一个函数作为子元素,这个函数接收当前的 Context 值并返回一个 React 元素。
! 如果使用 Consumer,代码会变得稍微复杂一些。以下是如何用 Consumer 替代 useContext
import React, { createContext, useState } from "react";
// 创建 Context
const MessageContext = createContext();
// 内层组件
const ChildComponent = () => {
return (
// 需要一个函数作为子元素,这个函数接收当前的 Context 值并返回一个 React 元素
<MessageContext.Consumer>
{({ message, setMessage }) => (
<div>
<div>{message}</div>
<button onClick={() => setMessage("来自内层组件的新消息!")}>
内层点击更新消息
</button>
</div>
)}
</MessageContext.Consumer>
);
};
// 中层组件
const MiddleComponent = () => {
return (
<>
<div>
中层组件
<ChildComponent />
</div>
</>
);
};
// 外层组件
const TextUseContextConsumer = () => {
const [message, setMessage] = useState("来外层组件的信息!");
return (
<>
{/* 向包裹的组件提供message, setMessage 内层组件可以访问和更新这些值 */}
<MessageContext.Provider value={{ message, setMessage }}>
<div>
<h4>外层组件</h4>
<MiddleComponent />
</div>
</MessageContext.Provider>
</>
);
};
export default TextUseContextConsumer;
-
对比: Provider比Consumer写法更加简洁,更加灵活,可读性强
React第三方Hook
ahooks 国内流行
比如:useMousePosition ,就有现成的