React Basic Knowledge Points
React Basic Knowledge Points
2025-10-30
如何理解JSX
- JSX = JavaScript XML,这是一种语法糖
- JSX语法,是可选的,但是 React建议使用
- JSX语法,浏览器不支持,使用@babel/preset-react进行编译。Babel 会把 JSX 转译成一个名为 React.createElement() 函数调用。
- JSX元素,是对象,也是变量,是表达式,不是字符串
- JSX可以任意嵌套,语法是使用 {} 包裹jsx元素
- JSX中使用 {/* 注释内容 */}
- 在JSX中,可以使用表达式(表达式也要使用{}包起来),但不能使用语句
- JSX元素的动态属性,也要使用 {} 包裹
- 在JSX中有三个属性的变化:className,htmlFor, tabIndex
- 在JSX中,新增属性有:key,ref,dangerouslySetInnerHTML
- JSX可防注入攻击(XSS)
- 自定义组件必须以大写字母开头。
- JSX支持属性展开语法 <Child {...obj} />。
- 布尔类型、Null 以及 Undefined 在JSX中会被忽略,不能当作文本进行渲染
- JSX是不可变对象,当JSX元素需要更新时,我们的做法是创建一个新JSX对象,render方法将重新执行并完成DOM渲染(背后的运行机理:this.setState()修改声明式变量,一旦声明式变量发生变化,React系统会生成新JSX对象,对应生成新的虚拟DOM,进而触发Diff运算,找出两个虚拟DOM变化的最小差异,最后把这个最小差异render到DOM上.
React类(组件) ==实例化后==》JSX元素(React元素)
TestJsx 是React类(组件)
<TestJsx/> React元素(JSX元素)
- jsx是 JavaScript XML,这是一种语法糖,是 react 推荐使用的语法
- jsx浏览器默认不支持这种语法,需要使用 @babel/preset/react 进行转译,背后会使用 react.createElement()这个函数进行调用,转译成浏览器能识别的语法
- jsx 是不可变对象,当jsx需要更新时,react会重新生成一个新的jsx对象,render方法将重新执行并完成DOM渲染(背后的运行机理:this.setState()修改声明式变量,一旦声明式变量发生变化,React系统会生成新JSX对象,对应生成新的虚拟DOM,进而触发Diff运算,找出两个虚拟DOM之间变化的最小差异,最后把这个最小差异 render 到DOM上)
- jsx 动态属性需要使用 花括号 {} 包裹 ,类选择器需要使用 className
- 自定义组件必须以大写字母开头
- JSX支持属性展开语法 <Child {...obj} />
- 布尔类型、Null 以及 Undefined 在JSX中会被忽略,不能当作文本进行渲染
渲染机制
- 渲染顺序
- 组件函数的执行顺序是父 → 子
- 父组件的 render 函数先执行,生成子组件的 React 元素。
- 子组件随后开始渲染
- useEffect 的执行顺序是子 → 父
- 子组件的 useEffect 回调先执行,父组件的 useEffect 后执行。
- 原因:React 按组件树的渲染顺序收集副作用,但执行时是反向的(类似栈的先进后出)。
- React 的渲染机制是当父组件的 state 或 props 发生变化时,会重新渲染组件及其子组件
- 通过 React.memo 或 shouldComponentUpdate 跳过不必要的子组件渲染
- 组件函数的执行顺序是父 → 子
父组件触发渲染
↓
父组件 render() 执行
↓
生成子组件的 React 元素
↓
子组件 render() 执行
↓
子组件 useEffect 回调被计划执行
↓
父组件 useEffect 回调被计划执行
↓
浏览器绘制 DOM(提交阶段)
↓
按子 → 父顺序执行 useEffect 回调
| 阶段 | 类组件顺序 | 函数组件顺序 |
|---|---|---|
| 渲染阶段 | 父 render → 子 render | 父 render → 子 render |
| 挂载副作用 | 子 componentDidMount → 父 componentDidMount | 子 useEffect → 父 useEffect |
| 更新副作用 | 子 componentDidUpdate → 父 componentDidUpdate | 子 useEffect → 父 useEffect |
| 卸载 | 父 componentWillUnmount → 子 componentWillUnmount | 父 useEffect 清理 → 子 useEffect 清理 |
顺序背后的设计原理
渲染阶段自上而下:
父组件需先确定自身结构和传递给子组件的 props,子组件才能正确渲染。
副作用阶段自下而上:
父组件的副作用(如 DOM 操作)可能依赖子组件的 DOM 或状态,需等待子组件完全就绪
优化父子组件渲染的实践建议
- 避免不必要的子组件渲染
- 用 React.memo 缓存子组件,仅当 props 变化时重新渲染。
- 父组件传递的 props 使用 useMemo/useCallback 缓存,避免引用变化导致子组件重渲。
- 减少深层嵌套
- 过深的组件层级会增加渲染递归成本,可通过状态管理库(如 Redux、Context)替代逐层传递 props。
- 合理拆分组件
- 将频繁更新的逻辑拆分为独立子组件,限制重渲范围
- 注意事项
- 避免在子组件中依赖父组件的副作用
- 如果父组件的 useEffect 修改了状态,子组件可能需要通过 useEffect 的依赖项监听变化。
- 优化子组件渲染
- 使用 React.memo 或 useMemo 避免不必要的子组件渲染。
- 避免在渲染函数中执行副作用
- 副作用应放在 useEffect 中,而不是渲染函数内。
- 避免在子组件中依赖父组件的副作用
state
如何定义state?只能在构造器中定义。
如何进一步理解state?
1、state是声明式变量,它被 this.setState()修改时,react 会生成一个新的jsx对象,对应会生成一个新的虚拟DOM,并触diff运算,找出他们之间变化的最小差异,通过 render 重新渲染,最终更新DOM视图。
2、state的定义发生构造器中,但是在构造器中不能使用this.setState()
3、要想改变视图,一定要使用this.setState()来修改state。
4、在React中,可以直接修改state,但是这不会触发diff运算,因此视图不会更新。
5、重要原则:当我们修改state时,要考虑新值是否由旧值计算而来,如果是建议使用this.setState(fn)的方式进行修改;如果不是,可以使用this.setState({})
6、this.setState()这个方法是异步的。但是在定时器内部(宏任务)使用this.setState()时,它却是同步的。(微任务==》异步 宏任务==》同步)
7、this.setState({}, fn),当setState()这个异步操作完成时,第二回调函数会执行,在其中可以拿到最新的state( 类似于VUe的监听器 )。
8、当多个this.setState()一起调用时,会被React自动合并成一个setState()操作,触发一次diff运算,一次render()。
9、this.setState()在修改声明式变量时,是一种浅合并。某个声明式变量被修改,不会影响其它声明式变量。
10、state是当组件实例的私有数据,state数据可向子元素传递,而不能向父元素传递,这被称为“React单向数据流”。
state 是声明式变量,要定义在构造器中
在React中,可以直接修改state,但是这不会触发diff运算,因此视图不会更新,要想改变视图,一定要使用 this.setState() 来修改state,但是在构造器中不能使用 this.setState()
当使用this.setState()修改声明式变量时,react 会生成一个新的 jsx对象,对应会生成一个新的虚拟DOM,然后触发 diff 运算 ,并找出他们之间变化的最小差异,通过 render 重新渲染,最终更新DOM视图。
this.setState()这个方法是异步的,但是在定时器内部(宏任务)使用 this.setState()时,它却是同步的。(微任务==》异步 宏任务==》同步)
重要原则:当我们修改 state 时,要考虑新值是否由旧值计算而来,如果是建议使用 this.setState(fn) 的方式进行修改;如果不是,可以使用this.setState({}),this.setState({} || fn ,fn ),第二个参数为一个函数,在其中可以拿到修改的,最新的 state( 类似于VUe的监听器 )
当多个this.setState() 一起调用时,会被 React 自动合并成一个 setState() 操作,触发一次diff运算,一次render()。
this.setState()在修改声明式变量时,是一种浅合并。某个声明式变量被修改,不会影响其它声明式变量。
state是当组件实例的私有数据,state数据可向子元素传递,而不能向父元素传递,这被称为“React单向数据流”
如何进一步理解 props
- 在React开发中,props的作用远远比state更强大
- 在类组件和函数式组件中,都默认有props
- props是父子组件之间的通信纽带
- props是不能修改的,因为 React 函数式组件使用的是 纯函数(唯一的输入,唯一的输出,入参不能修改)一个函数的返回结果只依赖于它的参数,并且在执行的过程中没有副作用,我们就把该函数称作纯函数
- props可以传递任何数据类型,还可以传递事件函数和JSX元素、组件
- props和state不能交叉赋值,它们没有任何关联
- 最新的React中,props验证是由一个第三库prop-types来实现的
state 和 props 的区别
- state 可以 通过 setState 或 useState 进行修改,而 props 是外部传入的数据,不能修改
- props的功能非常强大,可以传递任何数据类型,还可以传递事件函数和JSX元素、组件
生命周期
三个阶段 :
- 装载阶段(3)
- React组件实例化时,调用constrouctor()
- 在这个生命周期中,不能使用this.setState()-
- 在这个生命周期中,不能使用副作用(调接口、dom操作、定时器、长连接)
- 不能把props和state交叉赋值
- componentDidMount()
- 相当于是vue中的mounted()
- 它表示DOM结构在浏览器中渲染已完成
- 在这里,可以使用任何的副作用(调接口、定时器、DOM操作、长连接。。。)
- 在这里,可以使用this.setState()
- render()
- 是类组件中唯一的一个必须要有的生命周期
- 这个render函数必须要有return,return结果只要满足jsx语法都可以。
- 它的return返回jsx默认只能是单一根节点,但是在Fragment的语法支持下,可以返回多个兄弟节点。
- Fragment碎片的写法:
<React.Fragment></React.Fragment>,简写成<></> - 在return之前,可以做任意的业务逻辑(但不能使用this.setState())
- 每当this.setState修改声明式变量时,会触发diff运算,进而触发render()重新渲染。
- render()这个生命周期,在装载阶段和更新阶段都会运行。
- 当render()返回null时,不影响生命周期的正常运行。
- React组件实例化时,调用constrouctor()
- 更新阶段(2)
- shouldComponentUpdate()
- 它相当于是一个开关,如果它返回true则更新机制正常执行,如果返回false则更新机制停止.
- 在vue中是没有的,所在React面试经常问题。
- 它存在的意义:可以用于性能优化。但是基本上用不到,最新的解决方案是使用PureComponent。
- 理论上这个生命周期的作用是:用于精细地控制声明式变量的更新问题,如果被变化的声明式变量参与了视图渲染则返回true;如果被变化的声明式变量没有直接或间接参与视图渲染则返回false,以减少diff运算。
- render()
- componentDidUpdate()
- 相当于是vue中的updated(),它表示DOM结构渲染更新已完成,只发生在更新阶段
- 在这里,可以执行大多数的副作用,但是不建议
- 在这里,可以使用 this.setState(),但是必须要有终止条件判断,避免死循环。
- shouldComponentUpdate()
- 销毁阶段
- componentWillUnmount()
- 相当于是vue中的 beforeDestroy()
- 一般在这里清除定时器、长连接等其它占用内存的变量
- 在这里一定不可以使用 this.setState()
- componentWillUnmount()
类组件与函数式组件有什么区别
- 类组件,要用 class 关键字来定义,它有state状态,有生命周期、有this,有ref,有上下文。类组件的缺点是相对于函数式组件来说,运行速度相对较慢、性能较差。
- 函数式组件,默认啥都没有(除了props),也就是说默认没有类组件那些特性。函数式组件的好处是运行速度较快,性能更好。(使用 Hooks(v16.8)可以模拟出像类组件一样的众多特性)
- 类组件使用要实例化,而函数组件直接执行取返回结果即可
表单绑定(React 表单是单向绑定的)
- 受控表单:指的是表单的 value/checked 属性由 state 控制 ,绑定 onChange 事件,手动取值
- 非受控表单:指的是表单的value/checked属性不由state控制。
- 原则:在React开发中尽可能地都使用受控表单,但是有一个例外,就是文件上传
状态提升(是一种数据共享的理念)
- 要解决的问题:多个组件之间数据共享的问题
- 怎么做?具体的做法是,找到这几个组件的最近的父组件,把需要共享的状态数据定义在父组件中。
组合 vs 继承
- 组合和继承,都是 组件复用 的一种思想(方式), 但是 React 官方推荐使用 组合模式 来实现组件的复用。
- 组合的语法基础是:props.children 和 render props(自定义属性可以传递 React元素(jsx元素))
- 在父组件中间 插入标签 ,子组件直接使用 props.children 进行渲染
- render props ==> props 可以传递任何数据类型,还可以传递 事件函数 和 JSX元素、组件,子组件可以直接通过 props 进行调用
- 如果使用“继承”思想,如何复用组件?思路如下:
class QfModel extends React.Component {} // 基类
class QfDeleteModal extends QfModal {} // 删除类型Modal
class QfConfirmModal extends QfModal {}
class QfDeleteSmallModal extends QfDeleteModal = {}
conText(上下文)
- 作用:自上而下地向组件树中注入数据 (数据共享)
- 注意:在上下文的消费者(实际上就是那些被上下文包裹的组件)中不能修改上下文
- 怎么使用上下文呢?
- 使用 React.createContext() 创建上下文对象
- 使用上下文对象上的 <xxx.Provider value={xxx}> 组件,向React组件树注入数据
<ThemeCtx.Provider value={theme}>
<Layout />
</ThemeCtx.Provider>
- 使用上下文对象上的 <xxx.Consumer>{()=>()}</xxx.Consumer> 组件,使用上下文数据
<ThemeCtx.Consumer>
{
theme => (
<div style={{background:theme.background,color:theme.color}}>
<h1>测试上下文</h1>
</div>
)
}
</ ThemeCtx.Consumer>
- 在函数式组件中如何使用上下文数据呢?
- 第1种语法:return (
{ ctx => )} - 第2种语法:使用 useContext('上下文对象') 访问上下文数据
- 上下文在哪些第三库中会用到呢?React-Router,Mobx,Redux
高阶组件
- 高阶组件是一种 代码逻辑复用 的高级技巧,结合了 React 的组合特性,在很多第三方库 路由、mobx、redux 中有 用到,也叫高阶函数,本质上是一个函数(纯函数)
- 作用:高阶组件(也被称之为容器组件),是用来修饰、装修UI组件的,实现业务逻辑的复用。
- 语法详解:高阶组件(高阶函数)接受一个 UI组件(React类)作为入参,返回一个新的UI组件(React类)
export default WrapComponent => {
return props => (
<WrapComponent {...props} dialog="{dialog}" img="{img}" />
)
}
- 注意点: 当一个UI组件被多个高阶组件修饰时,props 会丢失,需要使用 ...props,保留高阶组件修饰后的 props 语法:
hocFn(UIComponent){
return <NewUIComponent {...this.props} />
}
- 属性继承 使用原则:一个高阶组件,一般只复用一个可以复用的逻辑。
如何创建 refs
- refs是提供一种访问在render方法中创建DOM节点或者React元素的方法,在典型的数据流中,props是父子组件交互的唯一方式,想要修改子组件,需要使用新的props重新渲染它,某些情况下,在典型的数据流外,强制修改子代,这个时候可以使用refs
- 我们可以在组件添加一个ref属性来使用,该属性是一个回调函数,接收作为其第一个参数的底层DOM元素或组件挂载实例
- 在函数组件中使用 useRef
import React, { useRef, useEffect } from 'react';
function InputComponent() {
const inputRef = useRef(null);
useEffect(() => {
// 获取输入框的宽度和高度
const { offsetWidth, offsetHeight } = inputRef.current;
console.log(`尺寸:${offsetWidth}px x ${offsetHeight}px`);
}, []);
return <input ref={inputRef} type="text" />;
}
通过 inputRef.current 可访问 DOM 节点的属性和方法(如 focus()、getBoundingClientRect() 等)
- 类组件中通过 React.createRef() 创建 ref,并挂载到实例属性上
import React, { createRef } from 'react';
class ButtonComponent extends React.Component {
constructor(props) {
super(props);
this.buttonRef = React.createRef();
}
componentDidMount() {
console.log(this.buttonRef.current.textContent); // 输出按钮文本
}
render() {
return <button ref={this.buttonRef}>点击我</button>;
}
}
- 跨组件传递 ref,使用 forwardRef 转发 ref
// 当父组件需要访问子组件的 DOM 节点时,使用 forwardRef 包裹子组件
const ChildComponent = React.forwardRef((props, ref) => (
<input ref={ref} placeholder="子组件输入框" />
));
function ParentComponent() {
const childRef = useRef(null);
useEffect(() => {
childRef.current.focus(); // 父组件控制子组件输入框聚焦
}, []);
return <ChildComponent ref={childRef} />;
}
useImperativeHandle 暴露方法
// 限制子组件对外暴露的接口,避免直接暴露整个 DOM 节点
const ChildComponent = React.forwardRef((props, ref) => {
const inputRef = useRef(null);
useImperativeHandle(ref, () => ({
focus: () => inputRef.current.focus(),
getValue: () => inputRef.current.value
}));
return <input ref={inputRef} />;
});
ref 的作用
- 对Dom元素的焦点控制、内容选择、控制
- 对Dom元素的内容设置及媒体播放
- 对Dom元素的操作和对组件实例的操作
- 集成第三方 DOM 库
Hooks (React 16.8 新增)
- useState
- 作用:用于定义声明式变量,模拟类组件中的 state
- useState在定义声明式变量时,一定要赋初始值
- useState定义的声明变量,要使用 set*系列方法去更新,不建议直接修改
const [msg, setMsg] = useState('')
- useEffect
- 作用:模拟类组件中生命周期的特性
- 如何理解副作用?你可以这么理解,只要不是生成JSX的业务代码,都可以认为是副作用
- 副作用包括定时器、调接口、长连接、DOM操作、第三库的初始化等
- useEffect 可以看做是 componentDidMount,componentWillUnmount 和 componentDidUpdate 这三个函数的组合
- useEffect 有两个参数 ,第一个参数为一个函数 (相当于生命周期里面的 componentDidMount )这个函数里面还可以 return 一个函数(这个 return 的函数相当于 componentWillUnmount),第二个参数为一个数组,相当于 componentDidUpdate ,(可以把相关联的声明式变量放在里面,每当变量发生变化。就会重新执行)
- 在函数组件中,useEffect 是用于执行副作用的主要钩子。当涉及到父子组件时,子组件的 useEffect 会在父组件的 useEffect 之前执行
useEffect(()=>{return ()=>{}}, [])
- useContext
- useMemo 计算属性
- useCallback 缓存函数
- useRef
useMemo 和 useCallback 有什么区别
- 相同点:useCallback 和 useMemo 都是性能优化的手段,类似于类组件中的 shouldComponentUpdate,在子组件中使用 shouldComponentUpdate, 判定该组件的 props 和 state 是否有变化,从而避免每次父组件render时都去重新渲染子组件。
- useCallback 和 useMemo 的区别是 useCallback 返回一个函数,当把它返回的这个函数作为子组件使用时,可以避免每次父组件更新时都重新渲染这个子组件
- useCallback 的返回值是函数,所以适合对组件的事件响应函数进行处理,避免每次执行都生成新的函数,进而导致子组件属性不同而重新渲染。
- useMemo 因为返回值是任意值,所以可用用来做高耗时操作的缓存处理,比如组件需要的一些子组件,比较耗时的纯数值计算等。
- 简单概括就是:useCallback 用来处理事件函数,useMemo 用来缓存 DOM
- useCallback 避免函数因父组件重渲染被重复创建,优化子组件性能。
路由 Hooks
- useParams:获取参数(内部封装)
- useHistory:通过js api操作路由跳转 push方法
- useLocation: 查看当前路由
- useRouteMatch: 挂钩尝试以与
相同的方式匹配当前URL。在无需实际呈现 的情况下访问匹配数据最有用。
Redux Hooks
- 用于替代 connect 这个高阶组件
- useSelector 相当于 mapStateToProps
- useDispatch 相当于 mapDispatchToProps
Hooks 使用需要注意什么?
- 版本要 v16.8+ 才支持
- 只能在函数式组件中使用
- 只能在函数式组件的最顶层使用hooks,而不能在 for 循环、if 等语句下面使用hooks
- 因为函数组件中可以使用useState声明多个state,hooks的状态管理依赖于数组,hooks重新渲染的时候依赖于固有的顺序,如在for循环、if等语句下使用hooks就有可能在重渲染时改变hooks的固有顺序,导致bug。这与hooks的实现原理有关系。
如何避免组件的重新渲染?
- React.memo() 这可以防止不必要地重新渲染函数组件
- PureComponent 这可以防止不必要地重新渲染类组件
- 这两种方法都依赖于对传递给组件的props的浅比较,如果 props 没有改变,那么组件将不会重新渲染。虽然这两种工具都非常有用,但是浅比较会带来额外的性能损失,因此如果使用不当,这两种方法都会对性能产生负面影响。
Mobx

状态管理(数据管理)
Flux(是一套数据架构的思想)是 Facebook 提出的
Vuex、Mobx、Redux它们都是 Flux 思想指导下的一种具体的解决方案
状态管理工具:可预测状态的数据容器。
在React技术栈:mobx 和 redux
一般情况下,小项目可以考虑使用 mobx 6 & mobx-react 7
如果是大项目,建议使用 redux & react-redux
如何使用:
从 mobx-react 中,引用 Provider
在App根组件中,使用<Provider store={store}} /> ,注入 store
在React组件中,使用 inject('要使用的store中的数据')(observer(props=>(<div></div>)))
- 以下mobx(v6) + mobx-react(v7) 用ES6语法、函数式组件为例,说明集成mobx架构的一般步骤:
- 1、安装mobx(v6),用面向对象语法编写store和子store的代码逻辑,参见store目录。
- 2、安装mobx-react(v7),在App根组件中,使用<Provider {...store} />
- 3、在React组件中,使用 inject('stort')(observer(props=>(<div></div>)))
先使用 observer() 将组件变为观察者,然后再使用 inject ()注入需要共享的数据
语法:inject('store')observer((UIComponent))
observer(UIComponent) 它的作用是把React组件变成观察者。
特点:当mobx中被观察的数据发生变化时,观察者自动更新。
inject('store')(UIComponent) 它的作用是注入mobx中的状态数据
特点:一旦注入成功,在props上就可以直接访问。
// 【[mobx6]定义子Store第一种写法】 makeAutoObservable
import { makeAutoObservable } from "mobx";
// makeAutoObservable(this) 作用是把当前对象上的成员属性变成observable变量,把成员方法变成action方法。
export default class UserStore {
constructor() {
makeAutoObservable(this);
}
token = "token";
changeMsg(payload) {
this.token = payload;
}
}
// 【[mobx6]定义子Store第二种写法】
// makeObservable 根据开发者的需求,把指定的成员属性变成observable变量,把成员方法变成action。
// 当在action中需要用到同步的async/await时,建议使用 flow,编写generator的语法。
// observable 它用于把一个变量变成可观察的,当它变化时,在观察者中可以自动更新
// action 表示把一个方法变成action,它可以直接修改observable变量
// computed 用于get操作,是计算属性,当它所关联的observable变量发生变化时,会重新计算
import { makeObservable, action, computed, observable } from "mobx";
export default class TodoStore {
constructor() {
makeObservable(this, {
msg: observable,
changeMsg: action,
length: computed,
list: observable,
updateList: action,
});
}
msg = "hello mobx 2";
changeMsg(payload) {
this.msg = payload;
}
get length() {
return this.msg.length;
}
}
React 中的组件间通信
- 父子组件通信 props
- 自定义事件
- 使用 context 上下文
- 使用 mobx、redux 状态管理
- 状态提升
redux


redux 用于定义 store 的
react-redux 提供高阶组件(connect) 和 Hooks 写法 ,用于把 react 和 redux 连接起来
Redux 一个可预测状态的数据容器,它是基于Flux思想而开源的项目
第一个3,指的是三个api:createStore / combineReducers / applyMiddleware
createStore 用来创建 store
combineReducers 用来合并多个 reducers
applyMiddleware 使用中间件
第二个3,指的是Redux工作流程中的三个核心概念,分别 Store、 Action(Dispatch)、 View
第三个3,指的是Store的三个特点:Store单一数据源、Store是只读的、Store 只能通过 Reducer 纯函数进行修改
技术栈:Redux / React-Redux / Redux-Thunk / Redux-Saga ....
三个 Api : createStore( ) / combineReducers( ) / applyMiddleware( )
import { createStore } from 'redux'
createStore( reducer,{},middleware(中间件)) 创建 store
combineReducers({}) 合并多个 reducer纯函数
import xxx from xxx
const rootReducer = combineReducers({
study,
count,
music,
user,
article
})
const store = createStore(
rootReducer,
applyMiddleware(thunk)
)
export default store
connect 高阶组件写法
使用 createState(reducer)创建 Store ,然后 在 react-redux 中 引入 Provider 组件,引入 Store,并在 Provider 组件上注入 Store ,然后继续在
react-redux 引入高阶组件 connect
connect 语法 :connect(fn1 ,fn2)(UIComponent)
fn1,fn2 两个都要返回一个对象
connect(mapStateToProps ,mapDispatchToProps)(UIComponent)
mapStateToProps 和 mapDispatchToProps 两个都要返回一个对象
mapStateToProps 的形参 是一个 store , // 把状态管理中的 state 映射到 props 上 ,然后就可以在 props 上进行访问
mapDispatchToProps 的形参是一个 dispatch
// 把状态管理中的 state 映射到 props 上 ,然后就可以在 props 上进行访问
const mapStateToProps = (store) => {
return {
msg: store.msg,
};
};
const mapDispatchToProps = (dispatch) => {
return {
changeMsg: () => dispatch({}),
};
};
页面使用;
export default connect(mapStateToProps, mapDispatchToProps)(UIComponent);
reducer 纯函数,用于修改 store 数据
let initState = {
msg: "hello redux",
};
// reducer 是一个纯函数,唯一的输入得到唯一的输出
// 在 redux 中 ,只能使用 reducer 来修改 store(state)
// 深入理解 action ,它只能是一个 Plain Object(一个对象,可以被深复制的)。它只能通过 diapatch (action={}),派发到 Store 中
function reducer(state = initState, action) {
// 因为 store 是 只读的,所以要进行深复制
let newState = { ...state };
switch (action.type) {
case "":
// do something
break;
default:
// do something
}
return state;
}
const store = createStore(reducer);
export default store;
redux Hook 写法,可以用来代替 connect 这个高阶组件
import { useSelector, useDispatch } from "react-redux";
// useSelector 相当于 mapStateToProps
const msg = useSelector((store) => store.study.msg);
// useDispatch 相当于 mapDispatchToProps
const dispatch = useDispatch();
dispatch(msgChange("hello world"));
redux-thunk 中间件
在 redux 中,dispatch 是同步的,它负责向 store 中派发 plain action object
redux-thunk 用于解决 redux 不支持异步 actions的问题
redux 只支持同步的 action ,需要使用中间件(redux-thunk 或者 redux-saga)解决异步 action
用于异步的 生成器, 必须要 return 一个函数,然后用 redux-thunk
redux-thunk 这个中间件,在 View - Store 之间起作用,它用于判断 action 是不是 函数(fn), 如果是就构建一个 plain object ,发送给 store
redux,实际上发生了 两次 dispatch ,第一次 redux 什么都没做,第二次才是真正后端数据放入 redux 中
const store = createStore( reducer , applyMiddleware ( thunk ) )
当需要使用多个 中间件时
import { compose } from 'redux'
const store = createStore(
reducer ,
compose( applyMiddleware ( thunk ), applyMiddleware ( xxx ))
)
- compose 解决 使用多个中间件时,嵌套过多的问题
- applyMiddleware ( thunk )( xxx )
- applyMiddleware 返回本身
中间件(Middleware)
其本质上一个函数,对 store.dispatch 方法进行了改造,在发出 action 和执行 reducer这两步之间,添加了其他功能
redux-thunk:用于异步操作
redux-logger:用于日志记录
React Fiber --- React 背后的算法
- React 15 中的 virtualDOM,这是旧的协调算法
- React Fiber 是 React 16 中新的协调算法
- react fiber是通过 requestIdleCallback 这个api去控制的组件渲染的“进度条”
- react 无法精确更新,所以需要react fiber把组件渲染工作切片
Redux-toolkit
npm install typescript @types/react @types/node redux react-redux @reduxjs/toolkit
持久化处理
src / store / slice / counterSlice.ts;
import { createSlice } from "@reduxjs/toolkit";
interface CounterState {
value: number;
}
interface stateType {
actions: () => void;
counter: CounterState;
}
export const counter = createSlice({
name: "counter",
initialState: {
value: 100,
},
reducers: {
increment: (state) => {
// Redux Toolkit allows us to write "mutating" logic in reducers. It
// doesn't actually mutate the state because it uses the Immer library,
// which detects changes to a "draft state" and produces a brand new
// immutable state based off those changes
state.value += 1;
},
decrement: (state) => {
state.value -= 1;
},
// action 接收页面传过来的参数
incrementByAmount: (state, action) => {
state.value += action.payload;
},
},
});
export const value = (state: stateType): number => {
return state.counter.value;
};
// Action creators are generated for each case reducer function
export const { increment, decrement, incrementByAmount } = counter.actions;
export default counter.reducer;
src / store / store.ts;
import { combineReducers, configureStore } from "@reduxjs/toolkit";
// 持久化
import { persistReducer, persistStore } from "redux-persist";
// defaults to localStorage for web
import storage from "redux-persist/lib/storage";
//
import counter from "./slice/counterSlice";
const persistConfig = {
key: "root", // key to identify your persisted data
storage, // where you want to store the data (e.g., localStorage)
whitelist: ["counter"], // state slices you want to persist, if not all
};
const reducer = combineReducers({
counter,
});
const persistedReducer = persistReducer(persistConfig, reducer);
export const store = configureStore({
reducer: persistedReducer,
middleware: (getDefaultMiddleware) =>
getDefaultMiddleware({
serializableCheck: false,
}),
});
export const persistor = persistStore(store);
在根文件(index.tsx | main.tsx | _app.tsx)
import { Provider } from "react-redux";
import { PersistGate } from "redux-persist/integration/react";
import { store, persistor } from "../store/store";
<Provider store={store}>
<PersistGate loading={null} persistor={persistor}>
<Component {...pageProps} />
</PersistGate>
</Provider>
react diff 算法 的原理是什么
- 传统diff算法通过循环递归对节点进行依次对比,然后判断每个节点的状态以及要执行的操作,效率低下,算法复杂度高,达到 O(n^3),react将 diff算法 进行一个优化,复杂度降为O(n)
- React中使用「三个层级策略」对传统的diff算法进行了优化
- 主要遵循三个层级的策略:tree层级、component层级、element层级
- Tree层级:DOM节点跨层级的操作不做优化,只会对相同层级的节点进行比较,只有删除、创建操作,没有移动操作
- Component层级:同一个类的组件,会继续往下diff运算,更新节点属性,如果不是一个类的组件,那么直接删除这个组件下的所有子节点,创建新的
- 如果确切知道该组件可以不用更新,可以使用react生命周期函数 shouldComponentUpdate() 方法进行判断优化,阻断不必要的 render
- Element层级:同层级的单个节点进行比较,(对于比较同一层级的节点们,每个节点在对应的层级用唯一的key作为标识)用唯一的 key 作为标识,通过 key 可以准确地发现新旧集合中的节点都是相同的节点,因此无需进行节点删除和创建,只需要将旧集合中节点的位置进行移动,更新为新集合中节点的位置
tree 层级:

components 层级:

vue 的 diff 算法与 react 的 diff 算法的区别
vue 的 diff 算法
在对新旧虚拟dom进行比较时,是从节点的 两侧向中间 对比;如果节点的 key值 与 元素类型 相同,属性值不同,就会认为是不同节点,就会删除重建
react 的 diff 算法
- 在对新旧虚拟dom进行比较时,是从节点的 左侧 开始对比,就好比将新老虚拟dom放入两个栈中,一对多依次对比;如果节点的 key值 与 元素类型 相同,属性值不同,react会认为是同类型节点,只是修改节点属性
- react diff算法也有明显的不足与待优化的地方:
- 应该尽量减少将最后一个节点移动到列表首位的操作
- 当一个集合把最后一个节点移动到最前面,react会把前面的节点依次向后移动,而Vue只会把最后一个节点放在最前面,这样的操作来看,Vue的diff性能是高于react的
两种 diff 算法的相同点
- 都只进行同级比较,忽略了跨级操作;常见的现象就是对数组或者对象中的深层元素进行修改时,视图层监测不到其变化,故不会对dom进行更新,需调用一些特质方法来告诉视图层需要更新dom
react-query
react-query是一个用于在 React 应用中管理服务器状态的库。它提供了一组强大的工具来处理数据获取、缓存、同步和更新,简化了与服务器交互的复杂性。react-query通过 hooks 提供了简单且声明式的 API,使得数据获取和管理变得更加容易和高效。- 主要功能
- 数据获取和缓存:自动缓存数据,并在需要时重新获取。
- 后台数据同步:在后台自动同步数据,保持数据最新。
- 错误处理:提供内置的错误处理机制。
- 分页和无限滚动:支持分页和无限滚动的数据获取。
- 数据预取:在用户需要之前预取数据,提高用户体验。
- 主要 Hooks
useQuery:用于获取数据。useMutation:用于提交数据或执行其他副作用。useQueryClient:用于管理和操作查询客户端。
- 安装
react-query
- 首先,安装
react-query和react-query-devtools(可选,用于开发调试):
npm install @tanstack/react-query
npm install @tanstack/react-query-devtools
- 配置
QueryClient
- 在应用的根组件中配置
QueryClient和QueryClientProvider:
import React from "react";
import ReactDOM from "react-dom";
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
import { ReactQueryDevtools } from "@tanstack/react-query-devtools";
import App from "./App";
const queryClient = new QueryClient();
ReactDOM.render(
<React.StrictMode>
<QueryClientProvider client={queryClient}>
<App />
<ReactQueryDevtools initialIsOpen={false} />
</QueryClientProvider>
</React.StrictMode>,
document.getElementById("root")
);
- 使用
useQuery获取数据
- 在组件中使用
useQuery获取数据: - 常用参数说明
- queryKey:唯一标识本次查询的 key,支持数组,推荐写成 'key', params。
- queryFn:异步获取数据的函数,支持接收 context(如 queryKey)。
- enabled:是否自动请求,false 时需手动调用 refetch。
- staleTime:数据多久后变为“过期”,单位 ms。
- cacheTime:数据在内存中缓存多久,单位 ms。
- refetchOnWindowFocus:窗口聚焦时是否自动重新请求,默认 true。
- select:对返回数据做二次处理(如只取 data.list)。
- onSuccess/onError/onSettled:请求成功/失败/结束时的回调。
- initialData:初始数据。
// 最基础写法
const { data, isLoading, error } = useQuery({
queryKey: ['userList'],
queryFn: () => api.getUserList(),
});
// 带参数写法(依赖 queryKey)
const { data } = useQuery({
queryKey: ['userList', params], // params 变化会自动重新请求
queryFn: ({ queryKey }) => api.getUserList(queryKey[1]),
});
// 手动触发(配合 enabled: false)
const { data, refetch } = useQuery({
queryKey: ['userList', params],
queryFn: () => api.getUserList(params),
enabled: false,
});
// 需要时手动 refetch()
// 数据处理
const { data } = useQuery({
queryKey: ['userList'],
queryFn: api.getUserList,
select: (res) => res.list, // 只取 list 字段
});
----------------------------------
const { isPending: tableLoading, data: roleListData } = useQuery({
queryKey: ["roleList"],
queryFn: async () => {
const res = await roleService.getRoleList();
// 保证返回值不是 undefined
return res ?? [];
},
enabled: true,
});
// 手动刷新数据,例如新增后,表格重新获取数据
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
const queryClient = useQueryClient();
// 在需要的地方,调用
queryClient.invalidateQueries({ queryKey: ["roleList"] }); // 刷新表格数据
import React from "react";
import { useQuery } from "@tanstack/react-query";
import axios from "axios";
const fetchTodos = async () => {
const { data } = await axios.get(
"https://jsonplaceholder.typicode.com/todos"
);
return data;
};
const Todos = () => {
const { data, error, isLoading } = useQuery(["todos"], fetchTodos);
if (isLoading) return <div>Loading...</div>;
if (error) return <div>Error: {error.message}</div>;
return (
<ul>
{data.map((todo) => (
<li key={todo.id}>{todo.title}</li>
))}
</ul>
);
};
export default Todos;
- 使用
useMutation提交数据
在组件中使用 useMutation 提交数据:
const mutation = useMutation({
mutationFn: async (params) => {
// 这里写你的接口调用
return await api.xxx(params);
},
onSuccess: (data, variables, context) => {
// 成功回调
},
onError: (error, variables, context) => {
// 失败回调
},
onSettled: (data, error, variables, context) => {
// 无论成功失败都会调用
},
});
常用参数说明:
mutationFn:必填,执行异步操作的函数(如接口请求)。
onSuccess:可选,操作成功时的回调。
onError:可选,操作失败时的回调。
onSettled:可选,操作结束时(无论成功失败)都会调用。
触发调用:
mutation.mutate(params); // 触发接口调用
// 或
mutation.mutateAsync(params); // 返回 Promise
import React, { useState } from "react";
import { useMutation, useQueryClient } from "@tanstack/react-query";
import axios from "axios";
const addTodo = async (newTodo) => {
const { data } = await axios.post(
"https://jsonplaceholder.typicode.com/todos",
newTodo
);
return data;
};
const AddTodo = () => {
const [title, setTitle] = useState("");
const queryClient = useQueryClient();
const mutation = useMutation(addTodo, {
onSuccess: () => {
// 在成功后无效化并重新获取 todos 查询
queryClient.invalidateQueries(["todos"]);
},
});
const handleSubmit = (e) => {
e.preventDefault();
mutation.mutate({ title, completed: false });
};
return (
<form onSubmit={handleSubmit}>
<input
type="text"
value={title}
onChange={(e) => setTitle(e.target.value)}
placeholder="Add a new todo"
/>
<button type="submit">Add Todo</button>
</form>
);
};
export default AddTodo;
总结
react-query是一个强大的工具,可以简化 React 应用中的数据获取和管理。通过使用useQuery和useMutation等 hooks,可以轻松地处理数据获取、缓存、同步和更新,提高开发效率和用户体验。
常用第三方库
- framer-motion
- 用于 React 的动画库
- LazyMotion:用于懒加载 framer-motion 的动画特性,以减少初始加载的包大小。
- domMax:framer-motion 提供的一个特性集,包含了所有 DOM 相关的动画特性。
- m:framer-motion 提供的一个用于创建动画元素的组件。
- @react-spring/web 动画库
- resend 是一个专为开发者设计的现代化邮件服务库
- nextjs-toploader Next版本路由进度条
- clsx
- 用于动态拼接 CSS 类名
- 在代码中用于组合条件类名(如 { 'motion-safe:animate-...': isDarkMode })
- 用于动态拼接 CSS 类名
clsx('absolute inset-0 z-10', {
'motion-safe:animate-[backdrop-flicker...]': isDarkMode
})
- color
- 用于颜色操作和转换
- 在代码中用于计算颜色亮度(color(colorPattern.activeColor).luminosity())
- 支持颜色格式转换、亮度/饱和度调整等操作
- 示例用途:根据颜色的亮度值动态生成径向渐变透明度
- npm-run-all 用于并行执行多个 npm 脚本
"scripts": {
"dev": "run-p next-dev watch-content",
"next-dev": "next dev -p 3456",
"watch-content": "node ./scripts/watcher.js"
}
react-activaction
- 实现类似于 vue 中 keep-alive 的作用
- import {Active} from "react-activaction"
umi
基于dva, 对 react 做了 2次封装 ,开箱即用,插件繁荣
umirc.ts 或者 config/config.js 为配置文件
哪些情况下不适合:
react 16.8 以下
node 10 以下
有很强的的 webpack 自主观念,自定义 webpack配置、自定义路由方案
应用:ant design pro
Vue 和 React 的区别
- 模板渲染方式的不同
React是通过 JSX 渲染模板
Vue是通过一种 拓展的HTML语法 进行渲染
- 组件逻辑复用的方式不同
Vue 使用 mixins(混合)
React 使用 Hoc(高阶组件:高阶组件本质上是一个函数,函数内部返回一个组件)
- vue 是双向绑定的响应式系统,数据发生变化,视图自动更新,而 react 是单向数据流,没有双向绑定,需要使用 this.setState 修改才能触发 diff 运算 ,视图更新
- 组件通信的区别
Vue中使用 provide/inject 实现跨组件的数据传递
React 中使用 context(上下文)实现跨组件的数据传递
数据流向的不同。react从诞生开始就推崇单向数据流,而Vue是双向数据流
数据变化的实现原理不同。react使用的是不可变数据,而Vue使用的是可变的数据
组件化通信的不同。react中我们通过使用回调函数来进行通信的,而Vue中子组件向父组件传递消息有两种方式:事件和回调函数
diff算法不同。react主要使用diff队列保存需要更新哪些DOM,得到patch树,再统一操作批量更新DOM。Vue 使用双向指针,边对比,边更新DOM
谈一谈 vue 和 react 的区别?
- 从编程范式的角度讲
- 在vue-loader、vue-template-compiler的支持下,vue可以采用SFC单文件组织的方式实现组件化;vue有指令,使用指令能够方便地渲染视图,vue表单是双向绑定的;vue组件是基于选项式的编程,常用选项有生命周期、计算属性、侦听器等;vue的组件库十分繁荣,自定义属性、自定义事件、自定义插槽是vue组件化的三大基础。众多社区中的vue轮子,在vue架构中被Vue.use注册即可使用。
- react的语法基础是JSX,react中没有指令,元素渲染、条件渲染、列表渲染、动态样式都是基于JSX语法的。在webpack环境中,要安装@babel/core、@babel/preset-react等,实现对JSX的编译。React表单是单向绑定的,推荐使用受控表单。组件封装可以是类组件,也可以函数式组件,其中props是React组件化的核心。
- 从组件通信的角度讲
- 在vue组件通信中,跨组件通信的手段极其丰富且灵活,常用的通信方案有父子组件通信、ref通信、事件总线、provide/inject、parent/children、listeners/attrs、slot插槽通信等。除此之外,在vue中还可以使用vuex 或 mobx 来实现跨组件通信。总体上来讲,vue的组件通信极其灵活,自上而下、自下而上都是容易实现的;也正是因为过于灵活,这会“诱惑”开发者容易滥用通信手段,导致vue项目呈现出“易开发、难维护”的现状。
- 在react中数据是单向数据流,在组件树中数据只能自上而下地分发和传递。state是组件自有的状态数据,props是父级组件传递过来的数据。在react中最最基本的通信方案是状态提升,还有React上下文也可以实现自上而下的数据流。鉴于react这种数据流的特性,即使集成了Redux仍然会呈现出单向数据流的特征,因此React数据流更容易被管理,配合Redux一起更适合做中大型的项目开发。
- 从底层原理的角度讲
- vue支持指令是因为背后有vue-template-compiler这个编译器的支持,把带有指令的视图模板转化成AST抽象语法树,进一步转化成虚拟DOM。vue的响应式原理是使用了 Object.defineProperty 进行了数据劫持,数据劫持发生vue组件的创建阶段,vue的响应式原理和mobx状态管理的响应式原理相似,这种响应式实现最早出现在 knockout 框架。如果要手写一个简单版本的vue,需要实现Compiler类(用于模板编译)、Watcher类(用于更新视图)、Dep类(用于依赖收集)、Observer类(用于数据劫持)、Vue类(构造函数)等。
- react自v16以后发生了很多变化,v16以后底层的“虚拟DOM”不再是简单JSON数据了,React采用了最新的Fiber(双向链表)的数据结构,作为“协调”(Diff)运算的基础数据。React背后还提供了强大的 react-reconciler 和 scheduler 库实现Fiber链表的生成、协调与调度。相比vue组件,react在较大组件方面的性能更高。如果要手写一个简易版本的React,其核心要实现以下功能,createElement(用于创建元素)、createDOM/updateDOM(用于创建和更新DOM)、render/workLoop(用于生成Fiber和协调运算)、commitWork(用于提交)等,如果还有支持Hooks,还得封闭Hooks相关的方法。
- 从社区发展和未来展望的角度讲
- vue生态繁荣,用户基础大。vue3.0和vite的诞生给vue生态增加了新的生命力,同时也给vue开发者带来了空前的挑战。vue3.0众多新特性,以组合API、更友好地支持TS为代表,使得vue3.0的写法更加灵活。上手vue3.0并不难,但,要想写出健壮的可维护性更强的vue3.0代码,并不容易,这需要广大的前端开发者有更强大的前端基础功,对MVVM有深刻的理解和沉淀。
- react生态稳步向前,背后有强大的Facebook开发团队,从类组件编程向Hooks编程的转化指明了前进的方向。React(v18)呼之欲出,让前端开发者对React更具信心。在国内,阿里系的React开源项目繁荣,给以开发者足够的信心,至少三五年内深耕React仍然大有可为。