• Edwin's Blog
  • 首页
  • 文章
  • 分类
  • 项目
  • 关于

React Basic Knowledge Points

React

React Basic Knowledge Points

2025-10-30

react

如何理解JSX

  1. JSX = JavaScript XML,这是一种语法糖
  2. JSX语法,是可选的,但是 React建议使用
  3. JSX语法,浏览器不支持,使用@babel/preset-react进行编译。Babel 会把 JSX 转译成一个名为 React.createElement() 函数调用。
  4. JSX元素,是对象,也是变量,是表达式,不是字符串
  5. JSX可以任意嵌套,语法是使用 {} 包裹jsx元素
  6. JSX中使用 {/* 注释内容 */}
  7. 在JSX中,可以使用表达式(表达式也要使用{}包起来),但不能使用语句
  8. JSX元素的动态属性,也要使用 {} 包裹
  9. 在JSX中有三个属性的变化:className,htmlFor, tabIndex
  10. 在JSX中,新增属性有:key,ref,dangerouslySetInnerHTML
  11. JSX可防注入攻击(XSS)
  12. 自定义组件必须以大写字母开头。
  13. JSX支持属性展开语法 <Child {...obj} />。
  14. 布尔类型、Null 以及 Undefined 在JSX中会被忽略,不能当作文本进行渲染
  15. 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时,不影响生命周期的正常运行。
  • 更新阶段(2)
    • shouldComponentUpdate()
      • 它相当于是一个开关,如果它返回true则更新机制正常执行,如果返回false则更新机制停止.
      • 在vue中是没有的,所在React面试经常问题。
      • 它存在的意义:可以用于性能优化。但是基本上用不到,最新的解决方案是使用PureComponent。
      • 理论上这个生命周期的作用是:用于精细地控制声明式变量的更新问题,如果被变化的声明式变量参与了视图渲染则返回true;如果被变化的声明式变量没有直接或间接参与视图渲染则返回false,以减少diff运算。
    • render()
    • componentDidUpdate()
      • 相当于是vue中的updated(),它表示DOM结构渲染更新已完成,只发生在更新阶段
      • 在这里,可以执行大多数的副作用,但是不建议
      • 在这里,可以使用 this.setState(),但是必须要有终止条件判断,避免死循环。
  • 销毁阶段
    • componentWillUnmount()
      • 相当于是vue中的 beforeDestroy()
      • 一般在这里清除定时器、长连接等其它占用内存的变量
      • 在这里一定不可以使用 this.setState()

类组件与函数式组件有什么区别

  • 类组件,要用 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

image-20220726174306123

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:用于管理和操作查询客户端。
  1. 安装 react-query
  • 首先,安装 react-query 和 react-query-devtools(可选,用于开发调试):
npm install @tanstack/react-query
npm install @tanstack/react-query-devtools
  1. 配置 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")
);
  1. 使用 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;
  1. 使用 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 })
  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 的区别

  1. 模板渲染方式的不同
React是通过 JSX 渲染模板

Vue是通过一种 拓展的HTML语法 进行渲染
  1. 组件逻辑复用的方式不同
Vue 使用 mixins(混合)

React 使用 Hoc(高阶组件:高阶组件本质上是一个函数,函数内部返回一个组件)
  1. vue 是双向绑定的响应式系统,数据发生变化,视图自动更新,而 react 是单向数据流,没有双向绑定,需要使用 this.setState 修改才能触发 diff 运算 ,视图更新
  2. 组件通信的区别
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仍然大有可为。

Table Of Content

  • 如何理解JSX
  • 渲染机制
  • 优化父子组件渲染的实践建议
  • state
  • 如何进一步理解 props
  • state 和 props 的区别
  • 生命周期
  • 类组件与函数式组件有什么区别
  • 表单绑定(React 表单是单向绑定的)
  • 状态提升(是一种数据共享的理念)
  • 组合 vs 继承
  • conText(上下文)
  • 高阶组件
  • 如何创建 refs
  • Hooks (React 16.8 新增)
  • useMemo 和 useCallback 有什么区别
  • 路由 Hooks
  • Redux Hooks
  • Hooks 使用需要注意什么?
  • 如何避免组件的重新渲染?
  • Mobx
  • React 中的组件间通信
  • redux
  • connect 高阶组件写法
  • redux Hook 写法,可以用来代替 connect 这个高阶组件
  • redux-thunk 中间件
  • 当需要使用多个 中间件时
  • 中间件(Middleware)
  • React Fiber --- React 背后的算法
  • Redux-toolkit
  • 持久化处理
  • react diff 算法 的原理是什么
  • vue 的 diff 算法与 react 的 diff 算法的区别
  • vue 的 diff 算法
  • react 的 diff 算法
  • 两种 diff 算法的相同点
  • react-query
  • 总结
  • 常用第三方库
  • react-activaction
  • umi
  • Vue 和 React 的区别
  • 谈一谈 vue 和 react 的区别?
ShareShareShareShare
© 桂ICP备2022007988号
© 2025 Implement By Edwin WB Li