React
React 是一个用于构建用户界面的开源 JavaScript 库,由 Facebook(现 Meta)于 2013 年首次发布。它专注于构建高效的、组件化的前端应用程序,广泛应用于现代 Web 开发中。
React 学习资料/工具
访问官方站点:前往 React 官方网站。
JavaScript 框架语法特性对比-中文版:前往 JavaScript 框架语法特性对比-中文版。
React 源码:前往 React 源码。
babel:前往 巴别塔
React 哲学(Thinking in React)
react最核心概念。使用react来写react。
状态(state
React UI = fn(state)
状态决定视图。state是变量、一般情况下,state改变后,组件也会更新。(useref)
常见的三种管理状态的方法:useState、useReducer、第三方状态库。
状态管理
组件内部状态
在React函数组件中使用可以使用useState、和useReducer定义组件内部的state,这个state是定义在组建的VDOM上的、即fiber节点(每个组件对应的js对象)
组件间的共享状态
组件状态提升
我们希望多个组件的state同步更新、可以将相关的state从组件上移除、将state房子公共父级组件、在通过props将state传递给每个组件在React外部管理状态
即使React的状态管理库、如Redux、MobX、Zustand、Recoil等以Redux为例
export default function createStore(reducer, enhancer){
if (enhancer){
return enhancer(createStore)(reducer);
}
let currentState;
let listenerIdCounter = 0;
let currentListeners = new Map();
function getState(){
return currentState;
}
function dispatch(action){
currentState = reducer (currentState, action);
currentListeners.forEach((listener) =>())
return action;
}
function subscribe (listener) {
const listenerId = listenerIdCounter++;
currentListeners.set(listenerId, listener);
return () => {
currentListeners.delete(listenerId);
};
}
dispatch ({type:`@@redux/INIT${randomString()}`})
return {
getState,
dispatch,
subscribe,
};
}
组件化
组件间通信使用props属性互相通信。在父子组件中用于父子通信、不可修改。
ReactDom组件
有对应DOM节点的组件、如div、span、img、a等。
类组件
在react18中类组件归类在Legacy React APIs中。
class App extends Component{
render(){
return (
<h1>Hello World</h1>
)
}
}
函数组件
在VDOM上渲染的函数,函数名就是组件名。是函数组件的子节点。本身没有节点。
function App(){
return (
<img
src="https://example.com/logo.png" className= "Logo" alt="logo"
/>
)
}
React内置组件
可以写作<></>,可以将多个jsx节点组合在一起。可以以编程方式衡量 React 树渲染性能。 可以在子组件加载显示后备方案。(logining) 严格模式,在开发模式下,会进行一些额外的检查,如:检查是否存在无效的组件、检查是否存在无效的属性等。Some.Context.Provider/SomeContext.Consumer
在 React 中,Context 是一种用于跨组件层级传递数据的机制,避免了通过逐层 props 传递数据的繁琐操作。它特别适用于需要在多个组件之间共享全局状态(如主题、用户认证信息等)的场景。
React 提供了两种方式来使用 Context:
SomeContext.Provider:用于提供上下文数据。 SomeContext.Consumer:用于消费上下文数据。 下面详细介绍它们的用法和原理。
- 创建 Context 首先,你需要使用 React.createContext 创建一个 Context 对象:
import React from 'react';
// 创建一个 Context 对象,默认值为 "light"
const ThemeContext = React.createContext('light');
export default ThemeContext;
React.createContext(defaultValue):
如果某个组件没有被 Provider 包裹,则会使用 defaultValue 作为默认值。 2. 使用 Provider 提供数据 Provider 是用来提供上下文数据的组件。它的 value 属性可以是任何值(如字符串、对象、函数等)。
import React from 'react';
import ThemeContext from './ThemeContext';
function App() {
return (
// 使用 Provider 提供 "dark" 主题
<ThemeContext.Provider value="dark">
<Toolbar />
</ThemeContext.Provider>
);
}
function Toolbar() {
return (
<div>
<ThemedButton />
</div>
);
}
function ThemedButton() {
return <Button />;
}
export default App;
Provider 的 value 属性会向下传递给所有子组件。 子组件可以通过 Consumer 或 useContext 来访问这个值。 3. 使用 Consumer 消费数据 Consumer 是一种直接消费上下文数据的方式,适合在类组件中使用。
import React from 'react';
import ThemeContext from './ThemeContext';
function Button() {
return (
<ThemeContext.Consumer>
{theme => (
<button style={{ background: theme === 'dark' ? '#333' : '#fff', color: theme === 'dark' ? '#fff' : '#333' }}>
I am styled by theme context!
</button>
)}
</ThemeContext.Consumer>
);
}
export default Button;
Consumer 必须是一个函数子组件。 它接收当前的上下文值作为参数,并返回 JSX。 4. 使用 useContext(函数式组件) 如果你使用的是函数式组件,推荐使用 useContext Hook 来消费上下文数据。
import React, { useContext } from 'react';
import ThemeContext from './ThemeContext';
function Button() {
const theme = useContext(ThemeContext);
return (
<button
style={{
background: theme === 'dark' ? '#333' : '#fff',
color: theme === 'dark' ? '#fff' : '#333',
}}
>
I am styled by theme context!
</button>
);
}
useContext 是 React Hooks 提供的 API,更简洁易读。 5. 动态更新 Context 如果需要动态更新 Context 的值,可以将 state 和 setState 放入 value 中。
import React, { useState } from 'react';
import ThemeContext from './ThemeContext';
function App() {
const [theme, setTheme] = useState('light');
return (
<ThemeContext.Provider value={{ theme, setTheme }}>
<Toolbar />
<button onClick={() => setTheme(theme === 'light' ? 'dark' : 'light')}>
Toggle Theme
</button>
</ThemeContext.Provider>
);
}
function Toolbar() {
return (
<div>
<ThemedButton />
</div>
);
}
function ThemedButton() {
return <Button />;
}
function Button() {
const { theme, setTheme } = useContext(ThemeContext);
return (
<button
style={{
background: theme === 'dark' ? '#333' : '#fff',
color: theme === 'dark' ? '#fff' : '#333',
}}
>
Current Theme: {theme}
</button>
);
}
export default App;
在这个例子中,theme 和 setTheme 被放入 value 中,允许子组件动态更新主题。 总结
Provider: 用于提供上下文数据。 数据通过 value 属性传递给所有子组件。
Consumer: 用于消费上下文数据。 适合在类组件中使用。
useContext: 是 Consumer 的替代方案,更适合函数式组件。
动态更新: 可以通过将 state 和 setState 放入 value 中实现动态更新。
函数:forwardRef/memo/lazy/
单向数据流
基础模型:修改数据不可以直接进行修改、而是使用setState函数进行修改。
React设计原理
组件化架构
定义: React 应用是由多个独立的、可复用的组件组成的。每个组件都有自己的逻辑和控制,可以管理自身的状态。
优势: 组件化促进了代码重用,简化了调试和测试,并允许团队成员并行工作于不同的组件上。
虚拟 DOM (Virtual DOM)
定义: 虚拟 DOM 是内存中的一个轻量级副本,它代表了真实的 DOM 树。当应用的状态发生变化时,React 首先更新虚拟 DOM,然后通过高效的算法确定对真实 DOM 所需的最小更改集。
优势: 减少了直接操作真实 DOM 的开销,提高了性能,特别是对于大规模的应用程序、以及跨平台。如rn、泰罗。
单向数据流(Unidirectional Data Flow)
定义: 数据只能在一个方向上传播:从父组件到子组件。这意味着状态是自上而下流动的,确保了数据流向的一致性。
优势: 提供了更清晰的数据流,使得应用程序更容易理解和调试。
JSX
定义: JSX 是一种 JavaScript 的语法扩展,允许你在 JavaScript 中编写类似 HTML 的代码。它不是必须使用的,但极大地方便了 UI 的描述。
优势: JSX 结合了 JavaScript 和声明式 UI 编写方式,使得代码更加直观易懂。
Hooks
定义: Hooks 是 React 16.8 引入的新特性,允许你在不编写类的情况下使用 state 和其他 React 特性。例如,useState 可以让你在函数组件中添加状态逻辑。
优势: 简化了功能组件的逻辑处理,减少了代码复杂度,同时增强了代码的可读性和可维护性。
声明式 UI
定义: 在 React 中,你描述你的 UI 应该是什么样子的,而不是如何达到那种状态。当底层数据模型发生变化时,React 自动更新 UI。
优势: 开发者只需关注数据的变化,无需手动操作 DOM 来更新视图,这使得代码更加简洁、直观。
高性能
定义: 通过虚拟 DOM 和高效的 diffing 算法,React 能够最小化对真实 DOM 的操作次数,从而提高渲染效率。
优势: 即使在复杂的 UI 应用中也能保持良好的性能表现。
JSX 语法
JSX 语法 是 React 中用于描述用户界面的一种声明式语法扩展。 虽然 JSX 看起来像 HTML,但它实际上是 JavaScript 的语法糖,最终会被编译成普通的 JavaScript 对象(React Element)。这些对象本质上是用来描述 DOM 结构的。
换言之 **jsx** (_jsxs、16.8版本前为createElement)就是为了生成elemnet对象的。
babel:前往 巴别塔
原代码
function App(){
return (
<div className="App">
<div>
<p>123</p>
<img src={logo} className= "Logo" alt="logo"/>
</a>
<a calssName="link"
href="lichcarlos.top"
target='_blank'
rel="noopener noreferrer">
我的网站
</a>
</div>
)
}
babel转译后的代码
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
function App() {
return /*#__PURE__*/_jsxs("div", {
className: "App",
children: [/*#__PURE__*/_jsx("p", {
children: "123"
}), /*#__PURE__*/_jsx("a", {
calssName: "link",
href: "lichcarlos.top",
target: "_blank",
rel: "noopener noreferrer"
})]
});
}
从转译后的代码可以看出jsx是采用了级组件的嵌套结构,每个组件都是一个对象,对象中包含组件的属性和子组件。以递归的形式构建出整个组件树。
/#PURE/
/#PURE/ 这样的注释在 JavaScript 代码中,特别是在使用 Babel 等工具进行编译时,是一种特殊的标注,称为“纯注释”(pure annotation)。它的主要目的是向诸如 Babel、Terser 或其他编译/压缩工具传达信息,表明被标记的函数调用是“纯”的,即该函数不会产生任何副作用。
tsx
React 数据结构
React 由jsx语法介绍react对象组成的树形结构,同理react的数据结构也是一颗对象组成的树。其中每个对象都包含一些属性和子对象。
react的遍历是深度优先遍历 DFS
以下是一个简单的 React 数据结构示例:
function ChildComponent(props) {
return <p>{props.message}</p>;
}
class ParentComponent extends React.Component {
constructor(props) {
super(props);
this.state = { message: "Hello from Parent" };
}
render() {
return (
<div>
<h1>Welcome to our application</h1>
<ChildComponent message={this.state.message} />
</div>
);
}
}
ReactDOM.render(<ParentComponent />, document.getElementById('root'));
在这个例子中,我们有两个组件:ParentComponent 和 ChildComponent。ParentComponent 包含了 ChildComponent,并通过 props 将信息传递给它。
element对象
_jsxs执行后返回一个对象,该对象包含了组件的属性和子元素。即为element对象。element对象就是_jsxs执行后的结果。
const App: any = function () {
return (
<div className="App">
<h2>
<a> Hello React </a>
</h2>
</div>
)
}
const app = App()
console.log(app)
fiber对象
react渲染过程
第一步
element对象
- 描述ui的方法
- JSX
- 模板语法
|
|
\ /
《-编译优化-》
|
|
\ /
第二步
fiber对象
- 运行时的核心模块
- reconciler
- renderer
|
|
\ /
《-调用-》
|
|
\ /
第三步
宿主环境API
|
|
\ /
《-显示-》
|
|
\ /
第四步
真实ui
fiber对象是React的运行时数据结构,是react执行过程中元素状态的描述。
合成事件
React 的合成事件(SyntheticEvent)是 React 提供的一种跨浏览器的事件系统。它对原生 DOM 事件进行了封装,确保了不同浏览器之间的行为一致性,并提供了一套统一的 API。
合成事件的核心概念 事件池机制:React 使用事件池来管理合成事件对象。事件对象在触发后会被回收到事件池中,以便复用,从而减少垃圾回收的压力。 跨浏览器兼容性:React 对底层的 DOM 事件进行了标准化处理,屏蔽了浏览器之间的差异。 批量更新:React 的事件处理函数会在 React 的调度系统中执行,可以与状态更新和生命周期方法很好地配合。 dispatchEvent 的作用 dispatchEvent 是原生 DOM API 中的一个方法,用于手动触发一个事件。React 的合成事件系统并不直接暴露 dispatchEvent,但可以通过原生 DOM 节点调用它。
原生 dispatchEvent 示例
const button = document.getElementById('myButton');
const event = new Event('click', { bubbles: true, cancelable: true });
button.dispatchEvent(event);
在这个例子中,我们创建了一个 click 事件,并通过 dispatchEvent 手动触发了该事件。
在 React 中使用 dispatchEvent 虽然 React 的合成事件系统不直接依赖于 dispatchEvent,但我们可以通过以下方式在 React 应用中触发事件:
方法 1:直接操作 DOM 通过 ref 获取 DOM 节点,然后调用 dispatchEvent:
import React, { useRef } from 'react';
function App() {
const buttonRef = useRef(null);
const handleClick = () => {
console.log('Button clicked!');
};
const triggerClick = () => {
const event = new Event('click', { bubbles: true });
buttonRef.current.dispatchEvent(event);
};
return (
<div>
<button ref={buttonRef} onClick={handleClick}>
Click Me
</button>
<button onClick={triggerClick}>Trigger Click</button>
</div>
);
}
export default App;
在这个例子中:
第一个按钮绑定了 onClick 事件。 第二个按钮通过 dispatchEvent 触发第一个按钮的点击事件。 方法 2:模拟事件(推荐) 如果不需要直接操作 DOM,可以通过调用事件处理函数来模拟事件触发,而不是使用 dispatchEvent:
import React, { useRef } from 'react';
function App() {
const handleClick = () => {
console.log('Button clicked!');
};
const triggerClick = () => {
handleClick(); // 直接调用事件处理函数
};
return (
<div>
<button onClick={handleClick}>Click Me</button>
<button onClick={triggerClick}>Trigger Click</button>
</div>
);
}
export default App;
这种方式更符合 React 的设计理念,因为它避免了直接操作 DOM。
注意事项 不要滥用 dispatchEvent:React 的设计哲学是尽量避免直接操作 DOM,因此应优先使用 React 提供的事件处理机制。 事件池机制的影响:React 的合成事件对象在事件处理函数执行完毕后会被回收,因此不能异步访问事件对象的属性。如果需要保存事件数据,可以使用 event.persist() 或者手动拷贝所需数据。
const handleClick = (event) => {
event.persist(); // 阻止事件对象被回收
setTimeout(() => {
console.log(event.target.value);
}, 1000);
};
总结
dispatchEvent 是原生 DOM API,用于手动触发事件。 在 React 中,可以通过 ref 获取 DOM 节点并调用 dispatchEvent,但这不是最佳实践。 推荐直接调用事件处理函数来模拟事件触发,以保持代码的 React 风格。
Diff
比较元素的三大原则
- 更新前后,层级一致
- 更新前后,类型一致
- 更新前后 key相同
Hooks
useState
useState 是 React 中的一个核心 Hook,用于在函数组件中管理状态。它允许你在函数组件中声明一个状态变量,并提供更新该状态的函数。
useState是异步的、为了性能优化:调用set函数会重新渲染、在队列中选取最后一次的值(天然自带防抖。
基本用法
import React, { useState } from 'react';
function Counter() {
const [count, setCount] = useState(0);
return (
<div>
<p>当前计数: {count}</p>
<button onClick={() => setCount(count + 1)}>增加</button>
<button onClick={() => setCount(count - 1)}>减少</button>
</div>
);
}
export default Counter;
在这个例子中:
useState(0) 初始化了一个状态变量 count,初始值为 0。
setCount 是更新状态的函数。
每次调用 setCount,React 都会重新渲染组件,并将新的状态值传递给 count。
useReducer
集中管理数据。
useState和useReducer差异对比
import { useState} from 'react'
export const App = () =>{
return (
const [count, setCount] = useState()
<>
<div>
<button onClick = {()= > setCount( count +1 )} >+1</button>
<p>{count}</p>
</div>
</>
)
}
``````js
import {useReducer} from 'react'
//默认值
const initState = {
count: -1
}
type State = type initState
//初始化函数,只走一次
const initFn = (state:State) => {
return {count:Math.abs(state.count)}}
}
//处理函数reducer,默认不执行
const reducer = (state:State,action:{type : 'add' | 'sub'}) = >{
switch (action.type) {
case "add":
return {count: state.count +1}
case "sub" :
return {count: state.count -1 }
default:
return state
}
}
export const App = () =>{
const [state, dispatch] = useReducer (reducer, initState, initFn)
return (
<>
<div>
<button onClick = { () => dispatch({type:'add'}) } >+1</button>
<button onClick = { () => dispatch({type:'sub'}) } >-1</button>
<p>{count}</p>
</div>
</>
)
}
useSyncExternalStore
const res = useSyncExternalStore(subscribe, getSnapshot, getServerSnapshot?)