-- 前端演进:
- 静态页 - ajax - Jquery(解决不同浏览器兼容);
- 需求简化代码、提高维护性 - 设计模式 - MVC;
- 工程化 - 模块化:angular2、Vue、React;
-- React 设计哲学 - 简洁之美
- 单项数据流:数据页面绑定、单项渲染 - ui = f(x);
- 虚拟DOM;
- 组件化(一致性、方便协作);
- 可预测:编写可预测、符合开发者习惯;
- 简化模型:JSX、界面更新、单项数据流;
- 衍生:React Natice(一次学习、多处书写)
-- React、Vue 区别
- 相同点:支持组件化、数据驱动视图、使用
vdom - 不同点:
React使用jsx、Vue模板拥抱html;React函数时编程、Vue声明式编程
版本变化
v16: 痛不欲生,更新非常快,大版本不兼容、v16.3 / 8 新特性特别多;
v17:
- 采取渐近升级和
angular2-13类似; - 事件委托机制改变,更改成根DOM容器;
- 删除事件池;
- useEffect 清理操作改为异步(过去同步);
- jsx不可返回 undefined;
- 移除 react native web 不需要的内部组件;
- 破坏性改变:onFocus onBlur;
v18: 并发渲染; 新 Susense 组件; 新特性 startTransition(防抖)
v19(无缝升级) actions 简化异步操作,自动处理状态和错误; useOptimistic / useActionState; 服务器组件和ServerActions; 表单改进; 支持样式表和异步脚本管理,提升资源加载灵活性
jsx
--
React Element
const App = () =>{
return (
<div className='hello'>app</div>
)
}
console.log(App()) // 如下
jsx 被转换为 React.createElement 函数
const App = () => {
return React.createElement('div', {
className:'hello'
}, 'App')
}
// 最终
{
'$$typeof': Symbol(react.element),
"key": null,
"props": { "children": "app" }, // 文字
"ref": null,
"type": "div" // 类型
}
React Component,组件实例 提供 this 变量,追踪组件,调用组件属性和方法(即所有实例会有生命周期方法或者组件状态)
import React from 'react'
export default class App extends React.Component {
render() {
return <div>App</div>
}
}
转换为:
{
'$$typeof': Symbol(react.element),
"key": null,
"props": {}, // 文字
"ref": null,
"type": () => { ... }
}
结论:
函数式组件直接调用并附带 Props;
类组件:创建实例再调用 render( React.Component 等价于DOM节点,也可以代表组件实例 );
setState
不可直接修改 state,必须使用不可变值(不能直接操作 state 中的值即对源数据修改)
increase = () => {
// this.state.count++ // 错误
this.setState({
count: this.state.count + 1 // 正确
})
}
不能直接对数组进行 push pop splice 等,违反不可变值
// 正确方法
this.setState({
list1: this.state.list1.concat(100),
list2: [...this.state.list2, 100],
list3: this.state.list3.slice(0, 3),
list4: this.state.list4.filter(item => item > 100),
list5: list5Copy
})
不能直接对 obj 进行属性设置,违反不可变值
// 正确方法
this.setState({
obj1: Object.assign({}, this.state.obj1, {a: 100}),
obj2: {...this.state.obj2, a: 100}
})
在版本 v16 / v17 自身的合成事件中为异步
this.setState(
{
count: this.state.count + 1
},
() => {
console.log(this.state.count) // 回调获取最新值
}
)
console.log(this.state.count) // 异步获取不到最新值
但版本 v16 / v17 dom 事件和 web api 是同步(V18 版本全部为异步 !!!)
// 1
setTimeout(() => {
this.setState({
count: this.state.count + 1
})
console.log(this.state.count) // 获取到最新值
}, 0)
// 2
componentDidMount() {
document.body.addEventListener('click', this.bodyClickHandler)
}
componentWillUnmount() {
document.body.removeEventListener('click', this.bodyClickHandler)
}
bodyClickHandler = () => {
this.setState({
count: this.state.count + 1
})
console.log(this.state.count) // 同步获取到最新值
}
当执行 setState 时,内部会定义 isBatchingUpdates = true,类似于开关,执行完毕为 false
increase = () =>{
// 开始:处于 batchedUpdates 状态
// isBatchedUpdates = true
this.setState({
count: this.state.count + 1
})
// 结束
// isBatchedUpdates = false
}
命中异步
increase = () => {
// 开始:处于 batchedUpdates 状态
// isBatchedUpdates = true
setTimeout(() => {
// 此时 isBatchedUpdates 是 false
this.setState({
count: this.state.count + 1
})
})
// 结束
// isBatchedUpdates = false
}
setState 在更新前会被合并,因为异步更新 count 本身并未改变,每次读取时都为同一结果
// 只会 +1
increase = () => {
this.setState({
count: this.state.count + 1
})
this.setState({
count: this.state.count + 1
})
}
函数式不会合并
increase = () => {
this.setState((prevState, props) => {
return {
count: prevState.count + 1
}
})
this.setState((prevState, props) => {
return {
count: prevState.count + 1
}
})
}
总结
<= react 17 :React 组件事件异步更新,DOM事件、定时器同步更新不合并 state(setTimeout 会将 React 组件事件进行批处理)
>= react 18 皆为异步更新,所有事件都自动 批处理(Automatic Batching)(React的渲染过程并不立即响应所有的状态变化,而是将它们放入一个事件队列中,当事件队列积累了一定数量的变化或者浏览器空闲时,才会一次性批量处理这些变化。)
合成事件
本质是 React 封装的SyntheticEvent组合事件(模拟出DOM事件所有能力,合成事件机制利于更好的兼容性和跨平台,避免频发解绑方便事件统一管理);
event.target事件发生元素(指向当前元素)event.currentTarget事件绑定元素也指向当前元素,但其实是假象- event.nativeEvent 是原生事件对象(Vue 也是原生事件对象)
- event.nativeEvent.target 指向当前元素,即当前元素触发
- 通过
event.nativeEvent.currentTarget可知:react16所有的事件都被挂 document (减少内存消耗)、react17/18指向 root
为什么使用合成事件机制:
兼容和跨平台、挂载到顶层减少内存消耗、避免频繁解绑、方便事件的统一管理
transaction 事务机制
以上在执行 increasse 函数,会先执行 initalize ,再执行函数本身,最后执行 close ,此为事务机制;
如图所示:
简单使用伪代码理解:
transaction.initalize = function(){}
transaction.close = function(){}
function method(){}
transaction.perform(method)
生命周期
周期图示:projects.wojtekmaj.pl/react-lifec…
注意:
getDerivedStateFromProps(替代componentWillReceiveProps)用于在组件接收新的props时更新其状态,它在组件挂载和更新时都会被调用,可以根据新的props和当前的state来计算并返回一个新的state,这个方法的返回值将会被传递给组件的render方法,从而更新组件的UI。
react-router-dom
安装
npm install react-router-dom
yarn add react-router-dom
pnpm add react-router-dom
- BrowserRouter 使用 HTML5 提供的历史 API 来保持 UI 与 URL 同步;
- HashRouter 通过在URL中添加哈希片段来工作(#),从而不会触发页面刷新并能绕过某些服务器限制;
- MemoryRouter 不依赖于实际的位置历史记录;相反,它是完全存在于内存中的位置管理器 (服务端渲染);
- Route 负责匹配当前请求的 URL 并决定是否渲染对应组件,当请求路径与指定路径相匹配时,关联组件将会被渲染到页面上;
- Routes 则是一个容器组件,用来包裹多个 Route 实例,其主要作用在于优化性能以及提供更合理的嵌套结构支持,替代旧版中的 Switch 。
import { BrowserRouter, Routes, Route, Navigate } from 'react-router-dom'
<BrowserRouter>
<Routes>
<Route path='/' element={<App />}></Route>
<Route path='/demo' element={<Demo />}></Route>
<Route path='*' element={ <404 /> }></Route>
</Routes>
</BrowserRouter>
NavLink、Link
本质 <a> 标签,to = '..' 表示返回上一级
| 组件 | 含义 |
|---|---|
| NavLink | 自带 active 属性(activeClassName) |
| Link | 创建可点击链接,这些链接会导航到不同的 URL 而不会重新加载页面 |
重定向
<Route path='/demo' element={ <Navigate to={'/'} /> }></Route>
function navigate() {
return <h2>{ <Navigate to={'/'} /> }</h2>
}
useNavigate
const navigate = useNavigate();
navigate('/home') // 通过事件进行跳转
函数式 useRoutes
router.ts
function Router(){
const routes = useRoutes([
{
path: "/",
element: <App />,
},
{
path: "*",
element: <NotFound />
},
]);
}
export default Router;
main.ts
import BaseRouter from './router'
import { BrowserRouter } from 'react-router-dom'
ReactDOM.createRoot(document.getElementById('root') as HTMLElement).render(
<BrowserRouter>
<BaseRouter />
</BrowserRouter>
)
create
createBrowserRouter、createHashRouter
import { createBrowserRouter } from "react-router-dom";
// 创建路由
const router = createBrowserRouter([
{
path: "/",
element: <Home />,
},
{
path: "*",
element: <NotFound />
},
]);
App.tsx
import { RouterProvider } from 'react-router-dom'
<RouterProvider router={router} />
基础路由
配置根地址:http://localhost:8080/app
const router = createBrowserRouter(
[
{
path: '/',
element: <App />
}
],
{
basename: '/app'
}
)
动态路由
import { useParams } from "react-router-dom";
function Order() {
const params = useParams();
return <span>商品ID:{params.goodsId}</span>;
}
// 一级
{
path: '/order/:orderId',
element: <Order/>
}
// 二级
{
path: '/goods/:goodsId/order/:orderId',
element: <Order/>
}
嵌套路由
Outlet(相当于 Vue 里面的 route-view 组件)展示子组件
// 父组件定义
function Goods() {
return (
<div>
<h2>商品主页</h2>
<Outlet />
</div>
)
}
{
path: '/goods',
element: <Goods />,
children: [
{
path: 'list',
element: (
<div>
<p>商品一</p>
<p>商品二</p>
</div>
)
},
]
}
加载器
使用:createBrowserRouter、createMemoryRouter、createHashRouter、createStaticRouter Loader、useLoaderData:数据加载器、优先加载 loader 再加载 element
{
path: '/order/:id',
element: <Order />,
loader: orderLoader
}
function orderLoader({ params }: any) {
return params.id;
}
// 登录拦截
function orderLoader({ params }: any) {
if (!sessionStorage.token) return redirect('/login')
return {
token: sessionStorage.token
}
}
// 可以直接执行接口,请求 json 文件
function orderLoader({ params }: any) {
if (!sessionStorage.token) return redirect('/login')
return fetch(`/${params.id}.json`)
}