React18的介绍
React是一个用于构建用户界面(
user interface
)的
JavaScript
库。
React主要用于构建
UI
,基于
MVC
设计,起源于
Facebook
(
meta
),当年用来架设
Instagram
网站,于 2013年
5
月开源。(
MVC1.0 MVVM2.0
)
Facebook创造
React
是为了解决一个问题:构建随着时间,数据
“
不断变化
”
的
“
大规模
”
应用程序。传统的 节点操作在性能上的瓶颈是很难突破的(DOM
节点的频繁渲染、覆盖更新等),代表为原生
JS
和 JQuery。
React
为了打破当年的节点驱动瓶颈,创造了全新的开发思想:
数据驱动:数据发生改变,视图自动更新,关注点都放在数据上,操作更简单。
最小粒度更新:数据变化后,
React
会对比发生变化的节点,仅更新变化的部分。(
diff 1.0 / fiber
2.0
最小粒度更新)
组件化开发:
React
都是关于构建可复用的组件
组件:具有结构、样式、行为的单一部件
react18新特性
并发性(Concurrency)
:并发性是一种能够同时执行多项任务的能力。在
React 18
中,它是作为主
要新特性出现的。
自动批处理
(Automatic Batching)
:为了提高性能,一组
React
将多个状态更新放入一次渲染中的操
作,被称为批处理。
让
SSR
支持
Suspense
转换
(Transition)
React
的语法偏向于原生
JS
MVC三层架构
M:model:模型,数据在这里面
V:View:视图,页面结构
C:Controllor:控制器,逻辑代码
页面可拆分成三部分:M层,V层,C层 | |
M层:管理数据 | |
V层:渲染页面,也会调用C层,数据来源于M层 | |
C层:通过C层将数据渲染上去, 修改M层数据 |
React创建项目
全局安装脚手架
yarn global add create-react-app || pnpm i -g create-react-app
以上两个命令都可以
使用脚手架创建项目
create-react-app react-demo # 项目名建议小写英文,多个单词中划线
启动项目
我们可以通过以下命令进行运行项目
cd react-demo # 进入项目
yarn start # 启动
创建一个组件
在创建组件之前先删除src中的文件,这些文件时初始的文件。
添加 src/App.js 组件文件
React
中的组件名用大驼峰
import React from 'react'
export default function App() {
return (
<div>App</div>
)
}
添加 src/index.js 入口文件
在入口文件中指定
DOM
容器,渲染引入的组件
// 入口JS文件
import React from 'react';
import ReactDOM from 'react-dom/client';
import App from './App';
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
<App />
);
JSX语法(重点)
基本概念
jsx:js的拓展语法,可以在jsx中写html,它相当于一个模板,js的语法糖
JSX 语法的本质:并不是直接把 JSX 渲染到页面上,而是内部先转换成了 createElement 形式,再渲染的。
JSX 的优点
JSX
执行更快,因为它在编译为
JavaScript
代码后进行了优化;
它是类型安全的,在编译过程中就能发现错误;
使用
JSX
编写模板更加简单快速。 、
JSX语法基础
JSX
注释:推荐使用
{/*
这是注释
*/}
;
JSX
中添加
class
类名:需要使用
className
来替代
class
;
htmlFor
替代
label
的
for
属性;
在
JSX
创建
DOM
的时候,所有节点必须有唯一的根元素进行包裹;
在
JSX
语法中,标签必须成对出现,如果是单标签,则必须自闭合;
在
JSX
语法中,渲染动态数据的地方使用一对
{
表达式
}
进行渲染
注意:表达式包含变量、数学运算、有返回值的函数、三元表达式等。表达式的定义是能够返回一个结
果的代码块
JSX的应用
可以嵌套哪些内容呢?
——
基本数据类型、数组、
JSX
元素、函数调用。被嵌套的变量必须用
{}
包起来。这种{}
语法是
JSX
的语法特色。在
JSX
中凡是被
{}
所包裹的东西,我们称之为表达式。
如果嵌套的变量是字符串、数值类型,
JSX
会把它们当作文本进行渲染。(
v-text
)
如果嵌套的变量是
undefined
、
null
、布尔值,
JSX
会忽略掉它们。
如果嵌套的变量是数组,
JSX
会把它们当作列表进行渲染(要加
key
)。(
v-for
)
如果嵌套的表达式是函数调用,这个函数的返回值必须是
JSX
支持的变量类型。
如果嵌套的变量是对象,在动态
style
等特殊语法环境下,是支持的。
当
JSX
元素的属性是动态变量时,也要用
{}
包起来。(
v-bind
)
注意
JSX
的
{}
中只能放置变量和表达式。
{}
中可以发生任何
“
运算
”
,但
“
运算
”
的结果必须返回的是
ReactNode
。不能返回
void
、
Symbol
、
BigInt
、
Map
、
Set
等其它数据类型。它默认具有防注入攻
击(
XSS
)
建议不要在
JSX
的
{}
写
JS
语句。
在
JSX
中添加注释,示例:
{/* something */}
React
组件名的首字母必须大写。
const msg = '一闪一闪亮晶晶!'
const count = 10
// const msg = '你好,世界'
const flag = true
const list = ['html', 'css', 'javascript','react']
const cls = 'demo'
// const str = <h2>我是一个h2标签</h2>
const styleobj = {
color: 'green',
backgroundColor: 'skyblue',
fontSize:'36px'
}
const title = '这个是一个介绍jsx语法的标题'
const str = '<h1>你好</h1>'
// jsx语法:在需要使用到动态数据的地方,替换成一对花括号即可,在花括号内加上变量名,即可渲染动态数据
// {表达式}:能够返回一个结果的代码块(变量,数学运算,三元表达式,(有return)的函数,正则表达式,)
// 常用的函数(表达式):map,every,some,include等
export default function First() {
return (
<div>
<h1 title={title}>一闪一闪亮晶晶</h1>
<h2>{msg}</h2>
<div>jsx渲染数字:{ count }</div>
<div style={styleobj}>jsx渲染字符串:{ msg }</div>
<div>jsx渲染布尔值:{ flag }</div>{/*undefined、null、布尔值会自动忽略*/}
<div>jsx三元表达式:{ flag ? '我是真的' : '我是假的' }</div>
<div style={ { color: 'red' } } id={ cls }>jsx渲染标签属性和样式</div>
<div>jsx渲染标签对象:{ str }</div>
<p>数学运算:{10 * 10}</p>
<div>jsx渲染数组:{list.map(v => <span key={v}>{v} </span>)}</div>{/*如果嵌套的变量是数组,JSX会把它们当作列表进行渲染(要加key)*/}
<div>
我喜欢的技术有下面几种:
{
// 在jsx中循环渲染的每个子元素一定要拥有一个唯一的key属性,该属性只能是数字或字符串,不建议使用索引
list.map(item => {
return <h2 key={ item}>{ item }</h2>
})
}
</div>
<p style={{ color: 'green', backgroundColor: 'red' }}>满天都是小眼睛</p>
{/* 相当于innerHTML */}
<p>{str}</p>
<p dangerouslySetInnerHTML={{ __html:str }}></p>
</div>
)
}
JSX语法中有三个不一样的属性
className
(相当于
html
中
class
属性)
htmlFor
(相当于
html
中的
for
属性)
tabIndex
(相当于
html
中的
tabindex
属性)
JSX语法中新增了三个属性
key
渲染一个列表时,或者渲染一个数组时,都要加
key
,为了让
“diff
运算
”
更加高效。
ref
用于快速地访问
DOM
对象或者访问
JSX
实例对象。
dangerouslySetInnerHTML
向
JSX
节点中插入一段可以参与渲染的
HTML
字符串(相当于
Vue
中的
v
html
)
JSX
的注意点
JSX
元素必须要用一个
元素或者空元素
包裹起来
return (
<div>
<h1>点击的次数:{count}</h1>
{/* 在这里将B和C引入 */}
{/* 给B一个单击事件的函数 */}
<B fn={aaa} />
<C />
</div>
);
JSX是不可变对象
1. 什么是不可变对象
对象不变性在任何编程语言中都是一个重要的概念。它会限制对象修改并防止不需要的更改。简而言之,对象的不变性就是将它的状态变为只读的
为什么不可变性在React中非常重要?
不可变性指的是不直接修改数据,而是使用新的数据替换旧的数据。
不可变性带来的优势:
1.
撤销和回退操作在开发中是很常见的,不直接在数据上进行修改,可以帮助我们更好的回溯数据。
2.
更容易跟踪数据的改变。
3.
方便确定
React
重新渲染的时机。
事件处理
添加事件
React
元素的事件处理和
DOM
元素的很相似,但是有一点语法上的不同:
React
事件的命名采用小驼峰式(
camelCase
),而不是纯小写。
使用
JSX
语法时你需要传入一个函数作为事件处理函数,而不是一个字符串
import React from 'react'
export default function Event() {
const handleClick = () => {
console.log('我被执行了')
}
return (
<div>
<button onClick={ handleClick }>按钮</button>
</div>
)
}
event对象
在这里,
e
是一个合成事件。
React
根据
W3C
规范
来定义这些合成事件,所以你不需要担心跨浏览器的兼容性问题。React
事件与原生事件不完全相同。
import React from 'react'
export default function Event() {
const handleClick = e => {
console.log(e, '我被执行了')
}
return (
<div>
<button onClick={handleClick}>按钮</button>
/*
如果需要给事件处理函数额外传入一个数据,可以在外面包一个箭头函数
<button onClick={(e) => handleClick(e, msg)}>按钮</button>
*/
</div>
)
}
React的状态定义及修改
useState
是一个
Hook
函数,让你在函数组件中拥有
state
变量。它接收一个初始化的
state
,返回是一 个数组,数组里有两个元素,第一个元素是当前状态值和另一个更新该值的方法。
这儿有一些需要记住的关键的点:
修改方法不会立即更新值
如果你需要用到之前的值更新状态,你必须将之前的值传递给该方法,则它会返回一个更新后的
值,
eg:
setMessage(previousVal => previousVal + currentVal)
如果你使用同样的值作为当前状态,则React
不会触发重新渲染。(
React
是用
Object.is
来做比较的)
useState 不像类组件中的
this.setState
合并对象,它是直接替换对象。
useState 和所有的
hooks
遵循相同的规则,特别是,注意这些函数的调用顺序。
正确的使用方式
import React, { useState } from 'react'
export default function StateCom() {
const [ msg, setMsg ] = useState('你好,世界')
const handleClick = () => {
setMsg('你好,李焕英')
}
return (
<div>
<h1>{ msg }</h1>
<button onClick={handleClick}>按钮</button>
</div>
)
}
// 循环渲染
import React, { useState } from 'react'
export default function Loop() {
const [list, setList] = useState(['html', 'css', 'javascript', 'vue'])
console.log(list)
return (
<>
<span>我擅长:</span>
{
list.map(v => <p key={ v }> { v }</p>)
}
</>
)
}
react hooks
useEffect
Effect Hook
可以让你在函数组件中执行副作用操作
数据获取,设置订阅以及手动更改 React
组件中的
DOM
都属于副作用。
不管你知不知道这些操作,或 是“
副作用
”
这个名字,应该都在组件中使用过它们。也可以模仿生命周期。
通过使用这个 Hook
,你可以告诉
React
组件需要在渲染后执行某些操作。
React
会保存你传递的函数 (我们将它称之为 “effect”
),并且在执行
DOM
更新之后调用它。在这个
effect
中,我们设置了 document 的
title
属性,不过我们也可以执行数据获取或调用其他命令式的
API
。
import React from "react";
import { useState } from "react";
import { useEffect } from "react";
export default function UseEffect() {
const [msg, setMsg] = useState("你好世界");
// 生命周期:componentDidMount 挂载后 componentDidUpdate 更新后 componentWillUnmount 卸载前
// useEffect(callback,[依赖项])
useEffect(() => {
const timer = setInterval(() => console.log("定时器执行"), 1000);
console.log("使用useEffect模拟componentDidMount生命周期");
// alert("Component has been mounted!");
return () => {
// 清除定时器
clearInterval(timer);
console.log("使用使用useEffect模拟componentWillUnmount生命周期");
};
});
useEffect(() => {
// 给useeffect传入第二个参数,第二个参数是一个数组,数组里是要监听的数据变量名
console.log("使用useEffect模拟componentDidUpdate生命周期");
}, [msg]);
return (
<div>
<h1>{msg}</h1>
<button onClick={() => setMsg("你好李焕英")}>改名</button>
</div>
);
}
// useEffect:执行副作用操作
// 处理清理逻辑: 通过返回一个函数,可以在组件销毁时执行一些清理逻辑,例如清除定时器、取消订阅等。
// 模拟生命周期
// useEffect有两个参数,一个是回调,一个是数组
useRef
ref
是一种访问
DOM
的主要方式。如果你将
ref
对象以
<div ref={myRef} />
形式传入组件,则无论该节点如何改变,React 都会将
ref
对象的
.current
属性设置为相应的
DOM
节点。然而,
useRef()
比 ref 属性更有用。它可以很方便地保存任何
可变值
。这是因为它创建的是一个普通
Javascript
对象。而 useRef() 和自建一个
{current: …}
对象的唯一区别是,
useRef
会在每次渲染时返回
同一个
ref
对象
。
useRef如何使用
import React, { useRef, useEffect } from 'react'
function ReactCustomComponent() {
const inputEl = useRef(null);
// `current` 指向已挂载到 DOM 上的文本输入元素
useEffect(() => inputEl.current.focus())
return (
<>
<input ref={inputEl} type="text" />
</>
);
}
memo、useMemo、useCallback
一句话概括:
memo
、
useMemo
、
useCallBack
主要用于避免
React Hooks
中的重复渲染,作为性 能优化的一种手段,三者需要组合并结合场景使用。
import React, { useState, useMemo, useCallback } from "react";
import B from "./B";
import C from "./C";
export default function A() {
// 声明数量状态
const [count, setCount] = useState(0);
// 修改数量的方法
// const handleClick = () => setCount((pre) => pre + 1);
// useMemo 缓存方法
// const aaa = useMemo(() => {
// return handleClick;
// }, []);
// const aaa = useMemo(() => () => setCount((pre) => pre + 1), []); //简写/高级方式
// useCallback 缓存方法
const aaa = useCallback(() => setCount((pre) => pre + 1), [count]);
const bbb = useCallback(() => setCount((pre) => pre - 1), [count]);
// const handleClick2 = () => setCount((pre) => pre--);
console.log("A组件更新了");
return (
<div>
<h1>我被点击了{count}次</h1>
<B fn={aaa} />
<C fn={bbb} />
</div>
);
}
import React from "react";
import { memo } from "react";
export default memo(function B(props) {
// console.log(props);
console.log("B组件更新了");
return <div onClick={props.fn}>B</div>;
});
// props 属性 父传子
import React, { memo } from "react";
export default memo(function C(props) {
console.log("C组件更新了");
return <div onClick={props.fn}>C</div>;
});
// memo 避免React Hooks中的重复渲染 缓存组件
注意:不要滥用
useMemo
、
useCallBack
使用useMemo
、
useCallBack
时,本身会产生额外的开销,
并且这两个方法必须和
memo
搭配使用,否
则很可能会变成负优化
。
因此,在实际项目中,需要结合实际场景,评估重复渲染和创建useCallBack/useMemo
的开销来判断到 底用不用useCallBack
、
useMemo
。
自定义hook
自定义
Hook
是一个函数,其名称以
use
开头,函数内部可以调用其他
Hook
。
在自定义
Hook
的
顶层
可以
无条件地
调用其他
Hook(
useState
,
useEffect
)
。
我们可以自由决定自定义
Hook
的
参数
和
返回值
。
自定义
Hook
必须
以
use
开头
,这样方便判断该函数内部是否调用了内部
Hook
React
能自动检查
Hook
是否违反了
Hook
的规则
(
见
Hook
规则部分
)
。
每次使用自定义
Hook
时,其中的
state
和副作用都是完全隔离的。
Hooks
和普通函数在语义上是有区别的,就在于函数中有没有用到其它
Hooks
。
就是说如果你创建了一个
useXXX
的函数,但是内部并没有用任何其它
Hooks
,那么这个函数就不
是一个
Hook
,而只是一个普通的函数。但是如果用了其它
Hooks
,那么它就是一个
Hook
。
自定义hooks
import { useEffect, useState } from 'react'
export const useResize = () => {
let [ width, setWidth ] = useState(document.body.clientWidth - 30)
const resizeFn = () => {
setWidth(document.body.clientWidth - 30)
}
useEffect(() => {
window.addEventListener('resize', resizeFn)
return () => {
window.removeEventListener('resize', resizeFn)
}
}, [])
return {
width,
setWidth
}
}
使用自定义Hook
import React from 'react'
import { useResize } from './useResize'
export default function Table() {
const { width, setWidth } = useResize()
return (
<div style={ { width, height: 200, backgroundColor: 'green' } }>我假装是一个
表格table</div>
)
}
Hook 规则
只在最顶层使用
Hook
不要在循环、条件或嵌套中调用
Hook
只在
React
函数中调用
Hook
在
react
的函数组件中调用
Hook
在自定义
Hook
中调用其他
Hook
要确保
Hook
的调用顺序在每次渲染中都是相同的
ahook的使用
ahooks是由蚂蚁
umi
团队、淘系
ice
团队以及阿里体育团队共同建设的
React Hooks
工具库。
ahooks 基于 React Hooks
的逻辑封装能力,提供了大量常见好用的
Hooks
,可以极大降低代码复杂度,提升开 发效率。
安装
$ npm
install
--save
ahooks
# or
$ yarn
add ahooks
使用
import
{
useRequest
}
from
'ahooks'
;
实用hooks
全新的
useRequest
异步管理
import React from 'react'
import axios from 'axios'
import { useRequest } from 'ahooks'
export default function Hooks() {
const getData = async () => {
return await axios.get('https://api.uomg.com/api/rand.qinghua', {
format: 'json' })
}
const { data, loading, error } = useRequest(getData, {
pollingInterval: 2000, // 轮询
loadingDelay: 300 // loading状态延时
// 其它配置项请参照官方文档
})
return (
<div>
{ loading && '正在加载中' }
{ error && '报错了' }
{ data && data.data.content }
</div>
)
}
hooks闭包陷阱
什么是闭包陷阱
闭包是指跨作用域访问变量。在 React
中,
Hooks
函数也是闭包。
React Hooks
的闭包陷阱与普通
JavaScript
中的闭包陷阱类似,但是由于
React Hooks
的设计,使用
Hooks
时可能会遇到一些特定的问 题。
React Hooks
中的闭包陷阱主要会发生以下几种情况:
在
useState
中的闭包陷阱
在
useEffect
中的闭包陷阱
在
useCallback
中的闭包陷阱
useState 陷阱
陷阱:【异步陷阱】
useEffect 陷阱
陷阱:【过期闭包】
useCallback 陷阱
陷阱:【获取父组件的值,不是最新】