React的生命周期
:React实例从被创建到被销毁的过程,React组件中包含一系列勾子函数(生命周期回调函数), 会在特定的时刻调用。
只有 class 组件才有生命周期,因为 class 组件会创建对应的实例,而函数组件不会 。
React 生命周期主要包括三个阶段
:挂载阶段,更新阶段,卸载阶段
class Test extends React.Component{
constructor(props){ // 构造函数
super(props)
this.state = {}
}
// 初始化,更新时会调用
static getDerivedStateFromProps(props,state){
// 必须返回一个对象,会和state合并
return {}
}
// 初始化渲染时使用
componentDidMount(){}
// 组件更新时调用 返回false 不更新
shoudComponentUpdate(prevProps,nextState){ return true }
// 组件更新时调用,返回的值会设置在componentDidUpdate的第三个参数
getSnapshotBeforeUpdate(prevprops,nextState) { return ''}
// 组件更新后调用
componentDidUpdate(preProps,preState,valueFromSnaspshot){}
// 组件卸载时调用
componentWillUnmount() {}
// 组件抛出错误
static getDerivedStateFromError(){}
}
挂载阶段
挂载阶段
:组件实例被创建和插入 DOM 树的过程
由ReactDOM.render()触发初次渲染
更新阶段
由组件内部this.setSate()
或父组件重新render
触发或强制更新forceUpdate()
卸载阶段
由ReactDOM.unmountComponentAtNode()
触发
componentWillUnmount()
:会在组件卸载及销毁之前直接调用;重要的勾子
render
:初始化渲染或更新渲染调用。componentDidMount
:在组件挂载成功之后调用,该过程组件已经成功挂载到了真实 DOM 上。一般在这个钩子中做一些初始化的事,例如:开启定时器、发送网络请求、订阅消息
componentWillUnmount
:在组件卸载成功之前调用,做一些收尾工作, 如: 清理定时器、取消订阅消息
已经废弃的勾子
componentWillMount
componentWillReceiveProps
componentWillUpdate
使用会出现警告,在React 18.0需要加上UNSAFE_
前缀才能使用,以后可能会被彻底废弃,不建议使用。
JSX(JS XML)
是一种类似于XML的JS扩展语法,用来简化创建虚拟DOM, 在React中可以方便地用来描述UI ,JSX 是React.createElement()的语法糖
JSX会编译成React.createElement(),React.createElement将返回一个ReactElement的js对象,编译工作交由babel操作
import React from 'react';
//JSX不是字符串, 也不是HTML/XML标签,最终产生的就是一个JS对象
var ele = <h1>Hello, JSX!</h1>
// 等价于
var element = React.createElement(
"h1",
null,
"Hello, world!"
);
从实际开发、性能优化、趋势分析三方面分析:
无状态组件(展示组件):只作展示、独立运行、不额外增加功能的组件,特点是复用性强,可分为代理组件
,样式组件
,布局组件
有状态组件(灵巧组件):包含业务逻辑和数据状态的组件称为有状态组件,或者灵巧组件,特点是功能丰富、复杂度高、复用性低,有状态组件分为 容器组件
和高阶组件
// 高阶组件 判断登录状态
const checkLogin = (WrappedCompont) => {
return props => {
const isLogin = true; // 这里是登录判断条件,根据实际处理,未登录则显示登录组件
return isLogin ? <WrappedCompont {...props} /> : <LoginPage />
}
}
// 调用高阶组件,函数写法
class Demo extends React.Component{}
const DemoPage = checkLogin(Demo)
// 装饰器写法
@checkLogin
class Demo extends React.Component{}
单层级通信
跨多层组件通信(如祖孙组件通信)
原生JS渲染页面:数据 => 真实DOM
React渲染页面:数据 => 虚拟DOM(内存中的数据)=> 真实DOM
原生的JS DOM操作非常消耗性能
虚拟DOM是一个用来描述真实DOM结构的js对象。虚拟DOM会通过ReactDOM.render进行渲染成真实DOM,呈现在页面上。
优点:减少了DOM操作,减少了回流与重绘,保证性能的下限,并极大的优化大量操作DOM时产生的性能损耗
缺点:首次渲染DOM时,由于多了一层虚拟DOM的计算,会比innerHTML插入慢。
diff算法是用于比较新旧虚拟节点树之间差异的一种算法,react采用3种类型进行对比,分别是树对比、组件对比、元素对比:
React16起,引入了Fiber架构,是为了使整个更新过程可以随时暂停和恢复,节点和树分别采用了FiberNode和FiberTree进行重构,FiberNode使用了双链表的结构,可以直接找到兄弟节点和子节点
React
用于动态构建用户界面的 JS UI库。
React特点:
虚拟DOM和Diff算法
,尽量复用DOM节点,减少与真实DOM的交互JSX
,代码的可读性更好组件化模式
,提高代码复用率、且让代码更好维护声明式编程
, 让编码人员无需直接操作DOM,提高开发效率react16以后的渲染流程是Fiber Reconciler
Fiber Reconciler特点
渲染过程基本可以划分为调和和提交两个阶段
react-router
的本质就是页面的URL
发生改变时,页面的显示结果可以根据URL
的变化而变化,可以实现无刷新的条件下切换显示不同的页面,可以通过前端路由实现单页面(SPA)应用。
react-router-dom的常用API:
router组件(BrowserRouter、HashRouter)
和HashRouter
就是router根组件,用于包裹整个应用
路由匹配组件(Routes 、 Route、switch)
适用于当匹配到第一个组件的时候,后面的组件就不应该继续匹配
v6版本中
替换
,
和
要配合使用,且必须要用
包裹
,当URL发生变化时,
都会查看其所有子
元素以找到最佳匹配并呈现组件
Navigation组件(Link、NavLink、Navigate、Redirect)
:修改URL,且不发送网络请求(路由链接)
:与组件类似,且可实现导航的“高亮”效果。
:只要
组件被渲染,就会修改路径,切换视图。
:用于路由的重定向
Outlet:当
产生嵌套时,渲染其对应的后续子路由。
React Router
对应的hash模式
和history模式
对应的组件为
和
,作为最顶层组件包裹其他组件。
hash模式
:通过监听浏览器 onhashchange 事件变化,查找对应路由应用。
history模式
:通过 html5 的history API 中新增的 pushState() 和 replaceState() 方法修改浏览器记录,改变页面路径。
区别
受控组件和非受控组件是两种不同的表单输入组件的概念,区别在于受不受react组件的控制
context的作用是为了避免在组件间层层传递变量,我们可以通过createContext(null)来创建一个新的context,新创建的context包含一个provider 和一个consumer
传递时,需要用Provider包裹父组件,通过value携带参数,在Provider包裹下的层层组件中,通过consumer包裹子组件来读取传递的变量
1、手动保存状态
配合componentWillUnmount生命周期,通过redux之类的状态管理框架对数据进行保存,通过componentDidMount进行数据恢复,但数据量大的时候比较麻烦
2、通过路由实现保存 react-router
这个方法实现比较麻烦,原理是因为react状态丢失是由于路由切换卸载了组件引起的,所以从根本上改变路由对组件的渲染行为,有以下方式
3、模拟真实功能
github 有类似的实现插件 react-activation
,实现原理是:由于react 会卸载掉处于固有组件层级内的组件,所以我们需要将children子属性抽取处理,渲染到一个不会被卸载的组件内,再使用DOM操作将其真实内容移到对应的内容,实现此功能
相同点
不同点
主要有两个限制:
只能在函数内部的最外层调用 Hook,不要在循环、条件判断或者子函数中调用
react用链表结构来严格保证hooks的顺序,在调用时按顺序加入链表中,如果使用循环、条件或嵌套函数很有可能导致数组取值错位,执行错误的 Hook。
只能在 React 的函数组件中调用 Hook,不要在普通JS函数中调用
由于Hooks依赖React的渲染机制和函数组件的特性,所以只能在React的函数组件中调用。在普通的JavaScript函数中使用Hooks是没有意义的,因为它们无法享受到React提供的状态管理和渲染优化的好处
链表(Linked List)是一种常见的数据结构,用于存储和组织数据。它由一系列节点(Node)组成,每个节点包含了数据和指向下一个节点的引用(指针或链接)。链表的特点是节点之间通过指针相互连接,形成一个链式结构,并且节点可以按需动态分配内存,灵活地插入、删除和修改数据。
shouldComponentUpdate()
中使用使用 deepCopy
和 deepCompare
来避免不必要的 render()
,但 deepCopy
和 deepCompare
一般都是非常耗性能的。这个时候我们就需要 Immutable
,Immutable
通过is
方法则可以完成对比,而无需像一样通过深度比较的方式比较,提高性能。类组件使用 React.PureComponent进行浅比较,从而控制 shouldComponentUpdate 的返回值避免子组件重新渲染。当传入 props 或 state 不止一层,或者传入的是 Array 和 Object 类型时,浅比较就失效了,当然我们也可以在 shouldComponentUpdate()
中使用使用 deepCopy
和 deepCompare
来避免不必要的 render()
,但 deepCopy
和 deepCompare
一般都是非常耗性能的。这个时候我们就需要 Immutable或Immer
(在 React 工作流中,如果只有父组件发生状态更新,即使父组件传给子组件的所有 Props 都没有修改,也会引起子组件的 Render 过程。如果子组件的 Props 和 State 都没有改变,子组件的不应该重新 Render )
函数组件使用React.Memo
(浅比较)来避免子组件的重复渲染,当传给子组件的派生状态每次都是新的引用时,还需要搭配useCallback
缓存函数,更加推荐使用ahooks中的useMemoizedFn
,与useCallback效果相同,但它不传入依赖项,函数引用地址不变
函数组件使用useMemo
缓存大量的计算
发布订阅模式跳过中间组件 Render 过程,比如祖孙组件通信,使用React.createContext 和React.useContext,这样就少了中间组件的渲染阶段
列表项使用 key 属性,提高渲染效率
避免使用内联css样式和匿名函数
在React中,组件是不允许返回多个节点的,使用React.Fragment或<>>避免添加额外的DOM
路由组件懒加载,React.Lazy和React.Suspense
lazy
函数配合import()
函数动态加载路由组件,路由组件代码会被分开打包
指定在加载得到路由打包文件前显示一个自定义loading界面懒路由加载
:用的时候才加载,如果不用路由懒加载,页面在第一次进入的时候,就请求了所有组件的数据,如果组件过多,页面可能会出现卡顿现象,应该是用户按哪个链接再请求哪个组件
实现原理:
通过React.lazy
配合Webpack import()函数
动态加载路由组件,路由组件代码会被分开打包
React.lazy 方法返回的是一个 lazy 组件的对象,与 Promise 类似它具有 Pending、Resolved、Rejected 三个状态,分别代表组件的加载中、已加载、和加载失败三中状态。
通过React.Suspense
渲染 React.lazy 异步加载的组件,主要通过捕获组件的状态去判断如何加载,当这个组件的状态为 Pending 时显示的是 Suspense 中 fallback 的内容,只有状态变为 resolve 后才显示组件。如果遇到网络问题或是组件内部错误,页面的动态资源可能会加载失败,为了优雅降级,可以使用 Error boundary(错误边界) 来解决这个问题。
useEffect(异步执行副作用)
: 在函数组件中执行副作用操作 (用于模拟类组件中的生命周期钩子) , 在执行 DOM 更新之后调用 。
useEffect可以看成是 componentDidMount
,componentDidUpdate
和 componentWillUnmount
三者的结合。
React15: 架构可以分为两层
在React15及以前,reconciler(协调器)采用递归的方式创建虚拟DOM,递归过程是不可中断的,如果层级很深,递归时间超过了16ms,用户交互就会卡顿
因此,React将递归改成异步可中断的Filber
在React中,副作用(Side Effects)是指执行与组件渲染结果无关的操作。这些操作可以包括但不限于:
redux是一个实现状态集中管理的容器,遵循三大基本原则
redux工作流程
view 调用store的dispatch接收action传入store,reducer进行state操作,然后view通过store提供的getState获取最新的数据
// Provider
<Provider store = {store}> <App /> </Provider>
// connection
import { connect } from 'react-redux'
// 把redux的数据映射到react的props中去
const mapStateToProps = state => {
return {foo: state.foo}
}
// 将redux中的dispatch映射到组件内部的props中去
const mapDispatchToProps = dispatch =>{
return {
toclick: () => { dispatch({type:'toclick'}) }
}
}
connect(mapStateToProps, mapDispatchToProps)(MyComponent)