【前端】react大全一本通
定期更新,建议关注收藏点赞。
内容源自本人以前的各种笔记,这里重新汇总补充一下。
目录
- 简介
- 生命周期函数
- PWA(渐进式Web应用)
- 使用教程
- JSX(JavaScript XML)
- 虚拟DOM
简介
React.js 是一个帮助你构建页面 UI 的库。React.js 将帮助我们将界面分成了各个独立的小块,每一个块就是组件,这些组件之间可以组合、嵌套,就成了我们的页面。
React.js 也提供了一种非常高效的方式帮助我们做到了数据和组件显示形态之间的同步。
React.js 不是一个框架,它只是一个库。它只提供 UI (view)层面的解决方案。在实际的项目当中,它并不能解决我们所有的问题,需要结合其它的库,例如 Redux、React-router 等来协助提供完整的解决方法。
用户断网后网页不会消失。
- 项目目录文件
my-react-app/
├── node_modules/
├── public/
├── src/
│ ├── api/
│ │ └── index.js
│ ├── components/
│ │ ├── common/
│ │ └── specific/
│ ├── hooks/
│ │ └── useCustomHook.js
│ ├── pages/
│ │ ├── Home.js
│ │ └── About.js
│ ├── redux/
│ │ ├── actions/
│ │ ├── reducers/
│ │ └── store.js
│ ├── utils/
│ │ └── helpers.js
│ ├── App.js
│ ├── index.js
│ └── ...
├── .gitignore
├── package.json
├── README.md
└── ...
- package.json
关于项目的基本信息,还有相关指令 npm,如npm run start… - .gitignore
不想传到git的文件可以定义在里面
#注释
要忽略的用相对路径
- public
存放静态资源,包含 HTML 文件、图标等。默认的 index.html 文件是 React 应用的入口 HTML 文件。
manifest.json provides metadata used when your web app is installed on a user’s mobile device or desktop. See https://developers.google.com/web/fundamentals/web-app-manifest/
在 manifest.json 文件中配置 PWA(渐进式Web应用)的快捷方式图标、网址跳转和主题颜色,可以通过以下属性来完成:
{"name": "My Progressive Web App","short_name": "My PWA","description": "This is a PWA example.","start_url": "/index.html", // 设置启动页的网址"display": "standalone", // 可选项: fullscreen, standalone, minimal-ui, browser"background_color": "#ffffff", // 设置背景颜色"theme_color": "#0000ff", // 设置应用主题颜色"icons": [{"src": "icons/icon-192x192.png", // 图标路径"sizes": "192x192", // 图标尺寸"type": "image/png" // 图标类型},{"src": "icons/icon-512x512.png", // 图标路径"sizes": "512x512", // 图标尺寸"type": "image/png" // 图标类型}],"scope": "/", // 限定PWA的范围"orientation": "portrait", // 设置应用的默认方向,可选 'landscape' 或 'portrait'"lang": "en", // 设置语言"dir": "ltr" // 文字方向: "ltr" (从左到右), "rtl" (从右到左)
}
- src目录 (项目源代码)
自动化测试:app.test.js
src 目录是我们主要编写代码的地方,包含了所有的 React 组件、样式和其他资源。通常会按照功能或组件类型来组织代码。
components 目录存放项目的所有 React 组件。通常,我们会按照组件的功能或页面进行子目录的划分。
// src/components/Header.js
import React from 'react';const Header = () => (<header><h1>My React App</h1></header>
);export default Header;
assets 目录存放项目的静态资源,如图片、字体、样式等。
App.js 是 React 应用的根组件,通常用于设置路由和全局状态管理。
// src/App.js
import React from 'react';
import Header from './components/Header';const App = () => (<div><Header /><main><p>Welcome to my React app!</p></main></div>
);export default App;
运行入口:index.js 负责引入和渲染,index.js 是 React 应用的入口文件,负责渲染根组件App到index.html 中的 root 节点。
// src/index.js
import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';ReactDOM.render(<App />, document.getElementById('root'));
//将app组件挂载到root节点下,则在div为root的模块下会显示app组件
// src/api/index.js
import axios from 'axios';const apiClient = axios.create({baseURL: 'https://api.example.com',headers: {'Content-Type': 'application/json'}
});export const fetchData = async () => {const response = await apiClient.get('/data');return response.data;
};
- React Hooks 主要是为了让你在函数组件中使用状态(state)、副作用(side effects)和其他 React 特性,而不必使用类组件。它们让代码更加简洁和灵活,也改善了组件的复用性和可维护性。下面是一些具体的好处:
简化代码结构
在类组件中,管理状态和生命周期方法通常需要编写冗长的类定义,使用 Hooks 后,你可以在函数组件中直接管理状态和副作用,代码更简洁,逻辑更清晰。
组件复用
Hooks 使得组件的逻辑更加可复用。例如,使用 useState 和 useEffect 可以使状态和副作用的处理变得更模块化,可以将逻辑封装成自定义 Hooks,在多个组件中复用,而不需要通过继承或高阶组件来共享逻辑。
更灵活的副作用管理
在类组件中,副作用(例如数据获取、订阅事件等)通常需要在生命周期方法中管理,可能比较难以维护。useEffect 能让你明确地控制副作用的执行时机,并且非常灵活地指定依赖项,避免不必要的副作用执行。
不再需要类组件
对很多开发者来说,类组件比较难理解和维护。Hooks 帮助你摆脱了类组件的复杂性,能够只通过函数组件来实现所有的功能。
性能优化
useMemo 和 useCallback 这两个 Hooks 可以帮助你优化性能,避免重复渲染和不必要的计算,提升应用性能,尤其是在大型应用中。
为什么React 要提供 React Hooks 这种组件,普通的Component类不好么?
React 引入 Hooks 的主要目的是为了简化组件的状态管理和副作用处理,尤其是对于函数组件来说,Hooks 提供了比类组件更简洁、更灵活的方式。让我们来看几个关键的原因:
简化代码结构:类组件需要手动处理生命周期方法(如 componentDidMount、componentDidUpdate 等),并且状态(this.state)和事件处理(this.setState)通常显得比较冗长和难以组织。而通过 Hooks,函数组件可以通过 useState 和 useEffect 等简化这些逻辑,使得组件代码更加简洁和易于理解。
逻辑复用:类组件之间的逻辑复用通常通过高阶组件(HOC)或者 render props 来实现,但这些方式往往会带来“嵌套地狱”或难以追踪的问题。而 Hooks 提供了一种更直观的方式来复用组件逻辑。例如,可以将某些副作用或状态管理逻辑抽离到自定义 Hook 中,使得代码更加模块化、可复用。
更好的组合性:函数组件和 Hooks 使得组合组件变得更加自然。你可以灵活地组合多个 Hooks 来实现复杂的行为,而不需要通过继承或者嵌套的方式来实现功能。
不再依赖 this:类组件中,this 的使用常常让开发者感到困扰,特别是对于初学者来说,理解和管理 this 的绑定可能很麻烦。Hooks 让函数组件脱离了 this 的依赖,使用起来更直观。
性能优化:React 在 Hooks 的实现上进行了优化,避免了类组件的某些性能瓶颈。例如,React 在内部管理 Hooks 时能做更多的优化,从而减少不必要的重新渲染和计算。
//hooks 目录用于存放自定义的 React Hooks。
// src/hooks/useCustomHook.js
import { useState, useEffect } from 'react';const useCustomHook = () => {const [data, setData] = useState(null);useEffect(() => {// Fetch data or perform other side effects}, []);return data;
};export default useCustomHook;const [count, setCount] = useState(0);
useEffect(() => {// 执行副作用代码
}, [count]); // 可选的依赖项数组
const value = useContext(MyContext);
const inputRef = useRef(null);
//useMemo 和 useCallback 用于优化性能,避免不必要的重新计算或函数重建。
- pages 目录用于存放页面组件,这些组件通常会包含路由配置。
// src/pages/Home.js
import React from 'react';const Home = () => (<div><h1>Home Page</h1></div>
);export default Home;
- redux 目录用于存放 Redux 的相关文件,包括 actions、reducers 和 store 配置。
// src/redux/store.js
import { createStore } from 'redux';
import rootReducer from './reducers';const store = createStore(rootReducer);export default store;
- utils 目录用于存放通用的工具函数。
// src/utils/helpers.js
export const formatDate = (date) => {return new Date(date).toLocaleDateString();
};
生命周期函数
react 16.3 之后,React 引入了新的生命周期方法和一些废弃的方法:
componentWillMount、componentWillReceiveProps 和 componentWillUpdate 已经被弃用。
getDerivedStateFromProps 和 getSnapshotBeforeUpdate 是新的替代方法。
React 的生命周期函数可以分为三个主要阶段:挂载 (Mounting)、更新 (Updating) 和 卸载 (Unmounting)。每个阶段都有不同的生命周期方法,用来处理不同的任务。
- 挂载阶段 (Mounting)
当组件被创建并插入到 DOM 中时,以下生命周期函数会被依次调用:
constructor(props)
在组件实例化时调用。通常用于初始化状态和绑定事件处理程序。
static getDerivedStateFromProps(nextProps, nextState)
在每次渲染之前调用,无论是因为父组件的更新,还是由于内部状态变化。返回一个对象来更新组件的状态,或者返回 null 不更新状态。
注意:这是静态方法,不能访问 this。
render()
必须实现的函数,返回 JSX,React 会将其渲染到 DOM 中。
componentDidMount()
组件挂载完成后调用。可以在这里执行网络请求或订阅事件等操作,通常用于需要获取数据或初始化的情况。 - 更新阶段 (Updating)
当组件的状态或 props 改变时,组件会重新渲染。更新阶段包括以下生命周期方法:
static getDerivedStateFromProps(nextProps, nextState)
这个方法也会在更新阶段被调用(与挂载阶段一样)。
shouldComponentUpdate(nextProps, nextState)
在组件重新渲染前调用,用于判断是否需要更新组件。返回 true 或 false。默认是 true,如果想要优化性能,可以通过此方法来避免不必要的渲染。
render()
在更新阶段会再次调用,重新渲染组件。
getSnapshotBeforeUpdate(prevProps, prevState)
在渲染输出 (render) 后,提交到 DOM 前调用。可以用来记录一些信息,或者进行一些操作(例如,获取滚动位置等)。
componentDidUpdate(prevProps, prevState, snapshot)
在组件更新后调用,可以访问更新前的 props 和 state,以及 getSnapshotBeforeUpdate 返回的快照信息。适合在组件更新后进行操作(例如,网络请求、DOM 更新等)。 - 卸载阶段 (Unmounting)
当组件从 DOM 中移除时,会调用以下生命周期方法:
componentWillUnmount()
在组件卸载前调用,用于清理工作(例如,取消网络请求、移除订阅等)。
错误处理阶段 (Error Handling)
React 16 引入了新的错误边界 (Error Boundaries),可以捕获子组件的渲染、生命周期方法、构造函数等错误:
static getDerivedStateFromError(error)
用于渲染备用 UI。接收错误信息并返回更新的 state。
componentDidCatch(error, info)
捕获错误后的回调函数,可以用于日志记录等操作。
PWA(渐进式Web应用)
是一种利用现代Web技术构建的Web应用,旨在提供类似本地应用(Native App)的体验。PWA不仅能够在网页上运行,还能在手机或桌面上像传统的移动应用一样进行交互,同时保留了Web应用的灵活性。它通过借助一些先进的功能,如Service Workers、Web App Manifest 和 Push Notifications 等,来提升用户体验、优化性能,并能在离线或低网速环境下依然保持可用性。
- PWA 的核心特性
- 响应式设计
PWA 应用能够适应不同尺寸的屏幕,提供跨设备的体验,无论是桌面、平板还是手机,都会自动调整布局。 - 离线支持
通过 Service Worker 技术,PWA 可以缓存资源,确保即便在没有网络连接的情况下,用户仍然可以使用应用。 - 安装体验
PWA 可以通过浏览器直接安装到设备的主屏幕,用户像安装本地应用一样,直接启动 PWA,而无需经过应用商店。安装后,PWA 会像原生应用一样运行,且不显示浏览器界面。 - 自更新
PWA 可以在后台自动更新内容和资源,使得用户始终体验到最新版本的应用。 - 推送通知
PWA 支持推送通知功能,即使在应用没有运行时,也能推送实时消息或提醒。 - 提高性能
由于 Service Worker 能够缓存重要资源,PWA 可以大幅度提高应用的加载速度,减少网络请求。
- PWA 的技术组成
- Web App Manifest
这是一个JSON文件,告诉浏览器如何显示应用,包括应用的名称、图标、启动页面、主题颜色等。它允许用户将 PWA 添加到主屏幕并像原生应用一样使用。
{"name": "My PWA App","short_name": "PWA","start_url": "/","display": "standalone","background_color": "#ffffff","theme_color": "#0000ff","icons": [{"src": "icons/icon-192x192.png","sizes": "192x192","type": "image/png"},{"src": "icons/icon-512x512.png","sizes": "512x512","type": "image/png"}]
}
- Service Worker
Service Worker 是一个在浏览器后台运行的脚本,它可以拦截网络请求,缓存资源并提供离线支持。Service Worker 还可以实现推送通知和后台数据同步等功能。
编写 Service Worker 注册脚本
在你的主文件(例如 index.js 或 app.js)中调用 registerServiceWorker
确保你的 manifest.json 文件配置正确
// registerServiceWorker.jsexport function register() {if ('serviceWorker' in navigator) {window.addEventListener('load', () => {const swUrl = `${process.env.PUBLIC_URL}/service-worker.js`; // 指定 Service Worker 的路径navigator.serviceWorker.register(swUrl).then(registration => {console.log('Service Worker registered with scope: ', registration.scope);}).catch(error => {console.log('Service Worker registration failed: ', error);});});}
}export function unregister() {if ('serviceWorker' in navigator) {navigator.serviceWorker.ready.then(registration => {registration.unregister().then(success => {if (success) {console.log('Service Worker unregistered');}}).catch(error => {console.log('Service Worker unregistration failed: ', error);});});}
}// index.js 或 app.jsimport React from 'react';
import ReactDOM from 'react-dom';
import App from './App';
import { register } from './registerServiceWorker'; // 导入 register 函数// 注册 Service Worker
register();ReactDOM.render(<React.StrictMode><App /></React.StrictMode>,document.getElementById('root')
);
创建 Service Worker 脚本
在 service-worker.js 中,你可以缓存应用资源,处理离线请求等。
// service-worker.jsconst CACHE_NAME = 'my-cache-v1';
const urlsToCache = ['/','/index.html','/styles.css','/script.js', // 你所有的静态文件,或者可以使用通配符
];// 安装阶段,缓存静态资源
self.addEventListener('install', event => {event.waitUntil(caches.open(CACHE_NAME).then(cache => {console.log('Opened cache');return cache.addAll(urlsToCache);}));
});// 激活阶段,清理过时的缓存
self.addEventListener('activate', event => {const cacheWhitelist = [CACHE_NAME];event.waitUntil(caches.keys().then(cacheNames => {return Promise.all(cacheNames.map(cacheName => {if (!cacheWhitelist.includes(cacheName)) {return caches.delete(cacheName);}}));}));
});// 拦截网络请求,使用缓存响应请求
self.addEventListener('fetch', event => {event.respondWith(caches.match(event.request).then(cachedResponse => {if (cachedResponse) {return cachedResponse; // 如果缓存中有匹配的资源,直接返回}return fetch(event.request); // 否则正常请求}));
});
- HTTPS
PWA 强烈推荐使用 HTTPS,因为 Service Worker 只能在安全的环境下运行,且 HTTPS 提供了更高的安全性。
使用教程
- 自定义组件开头必须大写,大写字母开头为组件,组件写完记得导出
export default TodoItem;
在别的组件里引入TodoItem组件import TodoItem from './TodoItem';
return(<TodoItem />)//如果有多个东西则用一个<div>包裹起来
- 父组件向子组件传递数据
//TodoList中
return(
<div><TodoItem content={item}/>//TodoItem中
render(){
return <div>{this.props.content}</div>
}//TodoList中我们还需要传给子组件index值
<ul>
{this.state.list.map((item,index)=>{return(<div><TodoItem index={index} content={item} /></div>)
}
</ul>
- 子调用父组件方法修改父组件
- 想要调用父组件方法需要将父组件方法传递给子组件
return(<div><TodoItem handleID={this.handleItemDelete} /></div>
)//TodoItem.js中
handleClick(){this.props.handleID(this.props.index)
}
//但是报错了 原因是这个this指向的不是父组件(方法是父组件的方法)
//修改为
{this.state.list.map((item,index)=>{return (<div><TodoItemcontent={item}index={index}handleID={this.handleItemDelete.bind(this)}//将 handleItemDelete 绑定到当前实例/></div>)}
}//const content ={ this. props}; 以后用content直接代替这一串
onchange={this.handleInputChange.bind(this) //直接写在属性里影响性能//修改
//绑定的统一都放在constructor下面
this.handleInputChange = this.handleInputChange.bind(this);
- 把画界面中的逻辑相关代码专门弄到新的函数里
getTodoItem(){return this.state.list.map((item,index)=>{return(<div><TodoItem index={index} content={item}deleteItem={this.handleItemDelete} /></div>)})
}
- this.setState更新方式
setstate是异步函数 不能立刻被执行
在 React 的 新版写法 中,setState 函数接收一个回调函数,而这个回调函数的作用是返回一个对象。这个回调函数的形式是通过箭头函数来写的,因此可能会造成上下文(this)的问题。下面提到的报错,是因为箭头函数的写法没有正确访问e.target.value
//在之前的位置
<ul>{this.getTodoItem()}
</ul>//旧版写法:
this.setState({inputValue:e.target.value
})//新版写法:
this.setState (() => ({inputValue: e.target.value
}))
}
//无奈报错
//如果箭头函数中没有正确引用 e.target.value,就会导致 作用域 问题。
//this.setState 是异步的,它会在当前执行栈清空后更新状态。
//因此,在某些情况下,e.target.value 可能会因为作用域问题无法正确获取。//解决方法:手动提取 e.target.value
//在箭头函数外面先将 e.target.value 存储到一个常量中,然后在回调中引用它。
handleChange = (e) => {const value = e.target.value; // 手动提取 e.target.valuethis.setState(() => ({inputValue: value}));
}//有了ref 不再需要e.target获取元素//旧版
handle(e){
const value = e.target.value;
}/*
ref 是 React 提供的一种方式,
它可以让你直接访问 DOM 元素或组件实例,避免了通过 e.target 来获取元素。
在 React 中,ref 提供了对组件内部 DOM 元素的直接引用,从而使得你能够更高效地进行 DOM 操作。
*/
import React, { Component } from 'react';class MyComponent extends Component {constructor(props) {super(props);// 创建 ref 对象this.inputRef = React.createRef();}focusInput = () => {// 直接通过 ref 聚焦到输入框this.inputRef.current.focus();};handleClick = () => {// 通过 ref 获取输入框的值const inputValue = this.inputRef.current.value;console.log(inputValue);};render() {return (<div><inputref={this.inputRef} // 将 ref 绑定到输入框元素type="text"/><button onClick={this.focusInput}>Focus the Input</button><button onClick={this.handleClick}>Log Input Value</button></div>);}
}export default MyComponent;//在函数组件中,ref 的使用也非常简单。使用 useRef 钩子来代替类组件中的 React.createRef()。
import React, { useRef } from 'react';function FocusComponent() {const inputRef = useRef(null); // 创建 refconst focusInput = () => {// 直接通过 ref 聚焦到输入框inputRef.current.focus();};return (<div><inputref={inputRef}type="text"placeholder="Click the button to focus"/><button onClick={focusInput}>Focus the Input</button></div>);
}export default FocusComponent;
为什么使用 ref 而不是 e.target?
简化代码:ref 让你可以直接访问到元素,而不需要通过 e.target 获取目标元素。这减少了代码的复杂度。
避免事件对象依赖:有时候事件对象(e.target)可能会带来不必要的复杂性,尤其是当事件处理函数比较复杂时,ref 提供了更清晰、更直接的访问方式。
更强大的 DOM 操作能力:ref 不仅仅用于获取值,它还可以用于获取元素的其他属性,如焦点、滚动位置等,甚至可以在需要时直接操作 DOM。
常见场景:使用 ref 的优势
访问 DOM 元素的值:例如,获取输入框的值,而不需要通过 e.target.value。
设置焦点:在用户交互后自动将焦点设置到某个输入框。
控制滚动条位置:直接操作某个容器的滚动条位置。
媒体控制:控制视频或音频元素的播放和暂停等。
- 定义默认值,即使没传值也不会警告
ZuJian.defaultProps = {test:'hello world'
}
- 设置属性接受强校验
import PropTypes from 'prop-types';
//在export default之前加入如下
ZuJian.propTypes ={content:PropTypes.oneOfType([PropTypes.number,PropTypes.string]),//必须是string/num类型之一 这行P要大写function1:PropTypes.func,//必须是func类型index:PropTypes.number,test:PropTypes.string.isRequired,//isRequired表示父组件一定要传递这个值给子组件 不然报警告
}
- 绑定事件
constructor(props){
super(props);
this.handleClick = this.handleClick.bind(this);//绑定方法二 更好 节约性能
}render(){
return (//绑定方法一:<div onClick={this.handleClick.bind(this)}>//如果只是一个函数,this会指向不明,所以需要绑定{this.props.content}</div>)}
)
- 启动
npm run start - render要求所有小组件必须包含在一个大的div中
- react里有一个fragment占位符可以替代最外层div标签 先引入
<Fragment></Fragment>
- constructor构造函数 最先被执行
- 引入css文件
import './style.css'
- 标签转译
标签转译的效果是指将输入的 HTML 标签(例如 < b >、< i >、< h1 > 等)转化为对应的渲染效果。在前端应用中,输入的 HTML 标签会被解析并直接渲染为 HTML 内容,而不是原始的标签字符串。例如,用户输入的 HTML 标签会被转译为实际的样式效果,例如加粗、斜体、标题等,而不是简单地显示 < b> 或 < i> 等标签。 - dangerouslySetInnerHTML人如其名,很危险,不要用,可能带来 XSS (Cross-Site Scripting) 安全风险。使用时要确保对输入的内容进行适当的处理(例如,过滤掉不安全的标签和属性),以避免安全问题。
XSS(Cross-Site Scripting,跨站脚本攻击)是一种常见的网络安全漏洞,攻击者通过在网页中注入恶意的脚本代码,利用浏览器执行这些脚本,从而窃取用户数据、操控网页内容或执行其他恶意操作。XSS 攻击通常发生在应用程序没有对用户输入进行充分验证和消毒时,恶意用户通过输入数据(如表单、URL 参数等)注入脚本代码,导致浏览器执行这些恶意脚本。
JSX(JavaScript XML)
JSX(JavaScript XML)是 React 提供的一种语法扩展,允许在 JavaScript 中写类似 HTML 的代码。它本质上是 JavaScript 的一种语法糖,用来描述用户界面的结构和布局。JSX 让开发者能够在 React 组件中更加直观地定义 UI,而不需要通过传统的 JavaScript API 方式(例如 React.createElement)来创建 DOM 元素。
使用jsx语法(带标签)必须要引入react import react from 'react';
因为 JSX 本质上会被转译成 React.createElement 调用,React 必须在代码中存在,以便这些 JSX 元素能够被正确处理和渲染。在 React 17 之前,通常需要在每个文件顶部引入 React。从 React 17 开始,JSX 的编译方式发生了变化,React 团队引入了一种新的编译器(@babel/preset-react 的更新版本)。通过这一变化,不再强制要求在每个文件中显式引入 React。这是因为,JSX 会在编译阶段自动从 React 中获取相关的功能,而不需要显式调用 React.createElement()。
因此,在 React 17 及之后的版本中,如果你只是使用 JSX,而不直接使用 React 对象中的其他功能(如 useState、useEffect 等),你可以省略引入 React。
- 为什么 JSX 仍然被广泛使用?
语法简洁
JSX 让开发者可以像写 HTML 那样写 React 组件的 UI,而不是写一堆 React.createElement 调用。这种语法更符合开发者的直觉,也更容易理解和修改。
React 官方推荐
React 官方强烈推荐使用 JSX,它不仅简化了组件的书写,还能让开发者更高效地进行开发。即便 React 17 以后支持了“自动引入 React”的功能,JSX 依然是默认推荐的方式。 - 使用 JSX 与不使用 JSX 的区别
//使用JSX
const Button = () => {return <button>Click me</button>;
};//最终转译成 JavaScript
//JSX 并不能被浏览器直接识别,它需要经过编译,通常通过 Babel 将 JSX 转换成 JavaScript。//不使用JSX
const Button = () => {return React.createElement('button', null, 'Click me');
};//在 JSX 中,你可以直接嵌入 JavaScript 表达式。表达式必须用大括号 {} 包裹
// 在 JSX 中,所有的动态内容都必须用大括号 {} 包裹。
const name = "John";
const element = <h1>Hello, {name}!</h1>;//JSX 与 HTML 的区别
// 在 HTML 中,你用 class 来定义元素的类名,但在 JSX 中,应该使用 className,因为 class 是 JavaScript 的保留字。
<div className="my-class"></div>
//同样的,标签中的for用htmlFor替换//在 JSX 中,自闭合标签必须明确写成自闭合格式。
//例如,<img> 标签在 HTML 中可以省略闭合标签,但在 JSX 中必须写成 <img />。//在 JSX 中,事件名是驼峰命名法,而不是小写的。
//onclick 在 HTML 中是 onclick,但在 JSX 中应该写作 onClick
<button onClick={handleClick}>Click me</button>
虚拟DOM
虚拟DOM的概念和性能优化问题,涉及到现代前端框架(尤其是 React)如何高效地更新和渲染用户界面。
- 第一次和第二次渲染:
在 传统的 DOM 操作 中,每次你修改页面内容时,浏览器都会直接更新页面的 DOM 结构。这样做有两个主要问题:
性能开销:每次 DOM 更新都涉及到重排(Reflow)和重绘(Repaint),这些都是昂贵的操作,尤其在复杂页面上表现得尤为明显。
不必要的重新渲染:即使你只修改了页面的一部分,浏览器通常会重绘整个页面,浪费了很多计算资源。
因此,每次更新都需要生成完整的 DOM 片段,然后将这些片段直接应用到真实 DOM 上。这个过程会消耗性能,特别是在有大量节点更新的情况下。 - 升级版 1.0:性能提升不明显
升级版 1.0 可能指的是一些基于传统 DOM 操作的优化方法,比如批量更新,或者减少对 DOM 的访问次数。
但是,虽然这些方法可以减少操作的次数,仍然会有不少性能瓶颈。特别是:
DOM 更新依然是昂贵的。
仍然存在很多不必要的渲染和 DOM 操作。
因此,性能提升可能不如预期,仍然会遇到效率瓶颈。 - 升级版 2.0:虚拟 DOM 的引入
虚拟 DOM(Virtual DOM)是 React 等框架引入的核心优化技术。它的工作原理大致如下:
JSX -> JS对象 -> 真实 DOM
JSX 是一种语法糖,最终会被转译为 JavaScript 对象。这个对象就是虚拟 DOM(Virtual DOM)。它只是一个描述真实 DOM 的数据结构,而不是实际的 DOM 节点。
虚拟 DOM 计算和比较:当你的组件状态或属性发生变化时,React 并不会立刻修改真实的 DOM,而是会先创建一个新的虚拟 DOM 树。然后,React 会通过diff 算法比较新旧虚拟 DOM 树之间的差异。
只更新差异部分:React 通过比较虚拟 DOM 树的差异(差异化算法(Diffing Algorithm)),找出需要更新的部分,然后只将这些差异应用到真实的 DOM 上。这样可以避免每次渲染时完全重建 DOM,从而大大提高性能。
批量更新:React 的虚拟 DOM 使得多个状态更新操作可以被批量处理,而不是一次次地触发实际 DOM 更新,进一步减少了性能开销。
- 虚拟 DOM 的过程
初始渲染:当组件首次渲染时,React 会生成一个虚拟 DOM,并将其与真实 DOM 进行比较(这时虚拟 DOM 和真实 DOM 是一致的),然后将虚拟 DOM 渲染到浏览器中。
状态变化:当组件的状态变化时,React 会生成一个新的虚拟 DOM 树。React 会将新旧虚拟 DOM 树进行比较,找出差异部分。
更新 DOM:React 根据比较出的差异,最小化地更新真实 DOM,避免重新渲染整个页面。