🤔思考
组件之间如何通讯
- props
- useContext
- redux
JSX的本质是什么
- js + template
Context是什么?如何应用?
- 上下文,避免子孙组件繁琐的传递操作
shouldComponentUpdate用途
- 性能优化,根据前后修改的props来判断是否要更新组件,使得组件更新更加精准。这也是react比较灵活和自立更生的一个特点
redux单向数据流画图表示
- 特点:state 不可变值,单向数据流
- reducer:纯函数,根据 action 更新 state。生成新的 state
- state:数据驱动视图
- store
- dispatch:分发 action
- action: type + payload
- 实现网络拦截
- redux-thunk: 异步 action,action 中进行网络请求

setState场景题

什么是纯函数
- 没有副作用,相同的输入,得到相同的输出
React组件生命周期
React发起ajax应该在哪个生命周期
- componentDidMount: 在页面渲染完之后再发起网络请求,这样不会阻塞页面的渲染,对用户友好
渲染列表,为何使用key
- vdom diff 的时候会用到 key 来做缓存,这样如果没有变的节点,会采取移动的操作,不会重新渲染,提高性能
函数组件和class组件的区别
- 函数组件中更多的是采取组合优先于继承的思想,使用上也简介,没有使用上下文this操作
什么是受控组件
- ref
何时使用异步组件
- 组件较大的时候,阻塞页面渲染效率,可以使用懒加载
- 有些组件不是每次都需要,可以使用懒加载
多个组件有公共逻辑,如何抽离
- HOC 输入一个函数,返回一个函数,但是是对这个函数做了修饰,例如在这个函数中获取到鼠标的坐标,可以统一在HOC做
redux如何进行异步请求
- redux-thunk
react-router如何配置懒加载

- import Lazy from ''; lazy(() => Component)
React 中 Function Component 与 Class Component 有何不同?
- Function Component 更简洁,没有 this 上下文,没有生命周期,通过 Hooks 来实现状态管理和生命周期
- useEffect
- useState
- Function Component 更简洁,没有 this 上下文,没有生命周期,通过 Hooks 来实现状态管理和生命周期
PureComponent有何区别
React事件和DOM事件的区别
React性能优化
- 升级框架,fiber
- shouldComponentUpdate中细化优化
- 用 webpack 等手段
- HTTP2
- SSR
React和Vue的区别
React 基础、使用
基础
JSX基础知识点
- 变量、插值
<p>{this.state.name}</p>
- 表达式
<p>{this.state.flag ? 'yes' : 'no'}</p
- 设置 class name
const classElem = <p className="title">设置 css class</p>
- 子元素和组件
// 子元素
const imgElem = <div>
<p>我的头像</p>
<img src="xxxx.png"/>
<img src={this.state.imgUrl}/>
</div>
- style
// style
const styleData = { fontSize: '30px', color: 'blue' }
const styleElem = <p style={styleData}>设置 style</p>
// 内联写法,注意 {{ 和 }}
// const styleElem = <p style={{ fontSize: '30px', color: 'blue' }}>设置 style</p>
- 插入原生 HTML
- 有跨站脚本攻击(XSS)的风险,不建议使用
// 原生 html
const rawHtml = '<span>富文本内容<i>斜体</i><b>加粗</b></span>'
const rawHtmlData = {
__html: rawHtml // 注意,必须是这种格式
}
const rawHtmlElem = <div>
<p dangerouslySetInnerHTML={rawHtmlData}></p>
<p>{rawHtml}</p>
</div>
return rawHtmlElem
插入例子
function createMarkup() {
return { __html: '<img src="empty.png" onerror ="alert(\'xss\')"/>' };
}
function MyComponent() {
return <div dangerouslySetInnerHTML={createMarkup()} />;
}
return MyComponent();

JSX如何判断条件和渲染列表
- if else
render() {
if (this.state.theme === 'dark') {
return <div>dark</div>
} else {
return <div>not dark</div>
}
}
- 三元表达式
- 逻辑运算符 && ||
与 Vue 不同,v-if v-else
<div v-if="type === 'A'"> A </div>
<div v-else-if="type === 'B'"> B </div>
<div v-else-if="type === 'C'"> C </div>
<div v-else> Not A/B/C </div>
渲染列表
- map with key
this.state.list.map((item, index) => (<li key={item.id}>index: {index}</li>))
React事件为何bind this
因为 JavaScript 函数的 this 是在运行时确定,确保 this 指向组件实例,需要在构造函数中绑定 this。
class Demo {
constructor(props) {
super(props)
// 修改方法的 this 指向
this.clickHandler1 = this.clickHandler1.bind(this)
}
clickHandler1() {
// console.log('this....', this) // this 默认是 undefined
this.setState({
name: 'lisi'
})
}
// 箭头函数不会创建 this, 继承外层作用域的 this,this 指向当前实例,所以不用 bind
clickHandler2 = () => {
this.setState({
name: 'lisi'
})
}
}
React event 事件和 DOM 事件的区别
- event 不是原生事件, 是 SyntheticEvent,模拟 DOM 事件的所有能力
- SyntheticEvent 合成事件:为了适配各个浏览器,react 封装了原生事件为合成事件
event.nativeEvent 是原生事件对象
React 16 之前,所有事件都挂载到 DOCUMENT 上,React 17 所有事件都挂载到 ROOT ELEMENT 上
- 有利于多个 React 版本共存,例如
微前端
4. 和 DOM 事件不一样,和 VUE 事件也不一样
// 获取 event
clickHandler3 = (event) => {
// event.preventDefault(); // 阻止默认行为
// event.stopPropagation(); // 阻止冒泡
console.log("target", event.target); // 指向当前元素,即当前元素触发
console.log("current target", event.currentTarget); // 指向当前元素,假象!!!
// 注意,event 其实是 React 封装的。可以看 __proto__.constructor 是 SyntheticEvent 组合事件
console.log("event", event); // 不是原生的 Event ,原生的 MouseEvent
console.log("event.__proto__.constructor", event.__proto__.constructor);
// 原生 event 如下。其 __proto__.constructor 是 MouseEvent
console.log("nativeEvent", event.nativeEvent);
console.log("nativeEvent target", event.nativeEvent.target); // 指向当前元素,即当前元素触发
console.log("nativeEvent current target", event.nativeEvent.currentTarget); // React 16 之前,所有事件都挂载到 DOCUMENT 上,React 17 所有事件都挂载到 ROOT ELEMENT 上
// 有利于多个 React 版本共存,例如 `微前端`
// 1. event 是 SyntheticEvent ,模拟出来 DOM 事件所有能力
// 2. event.nativeEvent 是原生事件对象
// 3. 所有的事件,都被挂载到 document 上
// 4. 和 DOM 事件不一样,和 Vue 事件也不一样
};
与 Vue 比较
- event 是原生的
- 事件被挂载到当前元素
- 和 DOM 事件一样
- 传递参数
- 最后追加一个参数,即可接收 event
// 传递参数
clickHandler4(id, title, event) {
console.log(id, title);
console.log("event", event); // 最后追加一个参数,即可接收 event
}
React 表单知识点
- 受控组件
可以简单理解为: input 受 state 控制,通过 onChange 手动修改 state 的值
render() {
return <div>
<p>{this.state.name}</p>
<label htmlFor="inputName">姓名:</label> {/* 用 htmlFor 代替 for */}
<input id="inputName" value={this.state.name} onChange={this.onInputChange}/>
</div>
}
onInputChange = (e) => {
this.setState({
name: e.target.value
})
}
input textarea select 使用 value
<textarea value={this.state.info} onChange={this.onTextareaChange}/>
<select value={this.state.city} onChange={this.onSelectChange}>
<option value="beijing">北京</option>
<option value="shanghai">上海</option>
<option value="shenzhen">深圳</option>
</select>checkbox radio 使用 checked
<input type="checkbox" checked={this.state.flag} onChange={this.onCheckboxChange}/>
<div>
male <input type="radio" name="gender" value="male" checked={this.state.gender === 'male'} onChange={this.onRadioChange}/>
female <input type="radio" name="gender" value="female" checked={this.state.gender === 'female'} onChange={this.onRadioChange}/>
<p>{this.state.gender}</p>
</div>
React父子组件通讯
- 父组件可以给子组件传递
数据或方法 - 在子组件中,通过 this.props 获取父组件传递的数据
- 状态提升:react 组件中,在公共的父组件中定义变量
setState 为何使用不可变值
不可变值
- 不要直接修改 state
- 不改变数组的函数
concat slice map filter reduce
let oldArr = [1, 2, 3]
console.log(oldArr.concat(100))// [1, 2, 3, 100]
console.log(oldArr)// [1, 2, 3]- 改变数组的函数:
push pop unshift shift forEach reverse
setState 是同步的还是异步的(要根据 react 的版本而定)
异步:直接使用时
同步:setState 回调中可以获取到修改后的值
this.setState({ count: this.state.count + 1}, () => {
console.log('count by callback', this.state.count)// 回调可以拿到最新值 1
})
console.log('count by callback', this.state.count)// 异步的,拿不到最新值 0同步:(react 16.x) setTimeout 中 setState 是同步
setTimeout(() => {
this.setState({ count: this.state.count + 1 })
console.log('count: ', this.state.count) // react 16.x 前,同步 1;批处理后 异步 0
}, 0);
console.log('count: ', this.state.count) // 异步,0异步 (react 17.x) 引入批处理更新机制, setTimeout 中取到的值也是没有修改过的
setTimeout(() => {
setState({ count: this.state.count + 1})
console.log('count in setTimeout', this.state.count)// 异步的,拿不到最新值 0
}, 0)
console.log('count in setTimeout', this.state.count)// 异步的,拿不到最新值 0
setState何时会合并state
- 传入对象,会被合并(类似使用了 Object.assign)
- 传入函数,不会被合并
// 传入对象,会被合并(类似 Object.assign )。执行结果只一次 +1
this.setState({
count: this.state.count + 1
})
this.setState({
count: this.state.count + 1
})
this.setState({
count: this.state.count + 1
})
// 传入函数,不会被合并。执行结果是 +3
this.setState((prevState, props) => {
return {
count: prevState.count2 + 1
}
})
this.setState((prevState, props) => {
return {
count: prevState.count2 + 1
}
})
this.setState((prevState, props) => {
return {
count: prevState.count2 + 1
}
})
React Component 组件生命周期

Render 阶段和 Commit 阶段
Render 阶段
- 生成 VDOM
Render 阶段是 React 根据组件的状态和属性计算出组件的新视图的过程。 在 Render 阶段中,React 会执行组件的 render 方法,生成 Virtual DOM 对象。 在此过程中,React 还会比较前一次的 Virtual DOM 对象和新生成的 Virtual DOM 对象之间的差异, 也就是所谓的
Reconciliation(协调)算法,找出需要进行 DOM 操作的地方。
Commit 阶段
- 提交 VDOM 的变化
commit 阶段是将 React 的 Virtual DOM 映射到真实的 DOM 上的过程。在 commit 阶段中, React 会根据前面 Render 阶段中计算出的需要进行 DOM 操作的地方,更新真实的 DOM,完成页面的渲染。
Render 阶段中的所有操作都是在虚拟 DOM 中进行的,真实的 DOM 并没有被操作。而 commit 阶段 则是将虚拟 DOM 的变化映射到真实 DOM 上的过程。这种分离的设计可以最小化 DOM 操作的次数,提高页面的渲染效率。
版本组件生命周期
React 16前、16.3、>= 16.4的生命周期
单个组件的声明周期
- 挂载
- constructor
- componentDidMount
- 更新(render: new props、new state、forceUpdate)
- shouldComponentUpdate
- componentDidUpdate
- 卸载
- componentWillUnmount
hooks模拟生命周期
模拟:
- DidMount
- DidUpdate
- WillUnMount
// // 模拟 class 组件的 DidMount 和 DidUpdate
// useEffect(() => {
// console.log('在此发送一个 ajax 请求')
// })
// // 模拟 class 组件的 DidMount
// useEffect(() => {
// console.log('加载完了')
// }, []) // 第二个参数是 [] (不依赖于任何 state)
// // 模拟 class 组件的 DidUpdate
// useEffect(() => {
// console.log('更新了')
// }, [count, name]) // 第二个参数就是依赖的 state
// 模拟 class 组件的 DidMount
useEffect(() => {
let timerId = window.setInterval(() => {
console.log(Date.now())
}, 1000)
// 返回一个函数
// 模拟 WillUnMount
return () => {
window.clearInterval(timerId)
}
}, [])
模拟 shouldComponentUpdate
- 通过 React.memo
高级特性
React 函数组件和 class 组件有什么区别
- 函数组件是纯函数,输入 props 输出 return JSX,就是这么简单
- 函数组件没有实例,没有生命周期,没有 state
- 函数组件编写简洁,没有这么多个 this....
- 不能扩展其他方法
选择原则
- 如果只输入 props 输出 JSX,那么推荐选择函数组件
- 如果有其他逻辑 比如要设置 state,对生命周期做一些处理,可以选择 class 组件。
- 也可以使用 useHooks + 函数组件
有哪些 useHooks
- useRef 与 createRef 区别
什么是 React 非受控组件
- ref
- defaultValue 和 defaultChecked
- 手动操作 DOM 元素
受控组件与非受控组件
input 来举例子
- 受控组件:通过
state自己控制值 - 非受控组件:通过
ref获取最后的值,输入的过程不受代码控制
非受控组件应用场景
- 必须手动操作 DOM 元素,setState 实现不了
- eg: 文件上传
<input type=file> - 某些富文本编辑器,需要传入 DOM 元素
- eg: 文件上传
相关实现
class App expends React.Component {
constructor(props) {
super(props)
thus.fileInputRef = React.createRef()
}
render() {
return <div>
<input type="file" ref={this.fileInputRef}/>
<button onClick={this.alertFile}>alert file</button>
</div>
}
alertFile = () => {
const elem = this.fileInputRef.current // 通过 ref 获取 DOM 节点
alert(elem.files[0].name)
}
}
受控组件 vs 非受控组件
- 优先使用受控组件 符合 React 设计原则,通过 state 来控制渲染。数据驱动视图
- 必须手动操作 DOM 的,选择非受控组件
什么场景需要使用 React Portals
Portals 提供将子节点渲染到父节点以外的方案
portals 官网翻译: 插槽 有道翻译:n. 门户网站;[建]入口
ReactDOM.createPortals(child, container)
场景
- overflow: hidden
- 形成 BFC ,影响子组件渲染,需要逃离父组件
- 父组件 z-index 值太小(被遮挡无法显示)
- fixed 需要放在 body 的第一层级
具体场景
- 对话框
- 提示框
- 悬浮卡
对比 "Vue slot 的具名插槽" 和 "react {this.props.children}"
// vue
<navigation-link url="/profile">
Your Profile
</navigation-link>
// 在 <navigation-link> 的模板中写为:
<a
v-bind:href="url"
class="nav-link"
>
<slot></slot>
</a>
// 当组件渲染的时候,<slot></slot> 将会被替换为“Your Profile”
// react
// 父组件
<demoComponent> Your Profile </demoComponent>
// 在子组件中 demoComponent 写为:
<div> {this.props.children} </div>
// 组件渲染 {this.props.children} 将会被替换为“Your Profile”
React Context
- 公共信息(语言、主题)如何传递给每一个组件
- 避免通过中间元素传递 props , 用 props 太繁琐(比如组件层级太深,需要一层一层传递,麻烦,难以维护)
- redux 大材小用
API
- React.createContext
- Context.Provider
- 消费者 Context.Consumer
实现
目的:底层组件,读取上层数据
- 创建 context
const ThemeContext = React.createContext('light')
- 组件间嵌套
- APP
- Toolbar
- ThemeLink、ThemeButton
// 底层组件 - 函数是组件
function ThemeLink (props) {
// const theme = this.context // 会报错。函数式组件没有实例,即没有 this
// 函数式组件可以使用 Consumer
return <ThemeContext.Consumer>
{ value => <p>link's theme is {value}</p> }
</ThemeContext.Consumer>
}
// 底层组件 - class 组件
class ThemedButton extends React.Component {
// 指定 contextType 读取当前的 theme context。
// static contextType = ThemeContext // 也可以用 ThemedButton.contextType = ThemeContext
render() {
const theme = this.context // React 会往上找到最近的 theme Provider,然后使用它的值。
return <div>
<p>button's theme is {theme}</p>
</div>
}
}
ThemedButton.contextType = ThemeContext // 指定 contextType 读取当前的 theme context。
function Toolbar(props) {
return (
<div>
<ThemedButton />
<ThemeLink />
</div>
)
}
class App extends React.component {
constructor(props) {
super(props)
this.state = {
theme: 'light'
}
}
render() {
<ThemeContext.provider value={this.state.theme}>
<Toolbar />
<hr/>
<button onClick={this.changeTheme}>change theme</button>
</ThemeContext.provider>
}
changeTheme() {
this.setState({
theme: this.state.theme === 'light' ? 'dark' : 'light'
})
}
}
export default App
思想
- 顶层组件 生产提供数据 管理数据
- 底层组件 消费数据
如何异步加载组件
- import() (类似 Vue)
- React.lazy
- React.Suspense
组件比较大时,或者路由懒加载时使用异步组件
const TestLazyComponent = React.lazy(() => import('./TestLazyComponent'))
class App expents React.Component {
constructor(props) {
super(props)
}
render() {
return <div>
<React.Suspense fallback={<div>Loading...</div>}>
<TestLazyComponent/>
</React.Suspense>
</div>
}
}
测试技巧
- 强制刷新,可以看到 loading (可通过限制模拟慢网速 chrome 来看到)
- 看 network 的 js 加载,懒加载会独立一个 js 文件懒加载
React性能优化
- 重点
- 性能优化对 React 更加重要
- 遇到性能问题才需要优化,结合业务和实际需求
SCU (shouldComponentUpdate)核心问题在哪里
问题
- 父组件 state 变化的时候,SCU 默认返回 true, 子组件都默认会渲染,即 React 默认重新渲染所有子组件,无论子组件中的 state 是否变化
核心
- SCU 默认返回 true,但是可以定制化,通过判断是否修改 state 或 props 来判断是否需要重新渲染
shouldComponentUpdate(nextProps, nextState) {
if (nextState.num !== this.state.num) {
return true // 值不同 渲染
}
return false // 不重复渲染
}
SCU 一定要配合不可变值
如果直接修改 state 的值,在 SCU 中判断是否变化的时候,有可能会判断为没有变化,导致没有渲染
setData () {
// 错误示范❌
this.state.num = 2
this.setState({
num: this.state.num
})
}
PureComponent 和 memo
PureComponent SCU 中实现了浅比较
- SCU 将跳过所有子组件树的 prop 更新。因此,请确保所有子组件也都是“纯”的组件。
- 用法
class List extends React.PureComponent {
...
}memo: 函数组件中的 PureComponent
- 包装组件
- 用法
function MyComponent(props) {
/* 使用 props 渲染 */
}
function areEqual(prevProps, nextProps) {
/*
如果把 nextProps 传入 render 方法的返回结果与
将 prevProps 传入 render 方法的返回结果一致则返回 true,
否则返回 false
*/
}
export default React.memo(MyComponent, areEqual);浅比较已适用大部分应用场景(尽量不要使用深度比较)
- 设计 state 时候,尽量不要设计过深结构
了解 immutable.js
- 一个 js module: Immutable data cannot be changed once created
Immutable.js provides many Persistent Immutable data structures including:
List, Stack, Map, OrderedMap, Set, OrderedSet and Record.
const { Map } = require('immutable');
const map1 = Map({ a: 1, b: 2, c: 3 });
const map2 = map1.set('b', 50);
map1.get('b') + ' vs. ' + map2.get('b'); // 2 vs. 50
React 高阶组件(HOC)
组件公共逻辑抽离
- mixin 已被 React 废弃
- 高阶组件 HOC (High Order Component)
- Render Props
class HOCFactory = (Component) => {
class HOC extends React.Component {
render() {
return <Component {} />// 返回拼接的结果
}
}
return HOC
}
const EnhancedComponent1 = HOCFactory(WrappedComponent1)
const EnhancedComponent2 = HOCFactory(WrappedComponent2)
eg: 为组件封装获取页面的坐标逻辑
案例
- redux connect函数 是高阶组件
import { connect } from "react-redux";
const VisibleTodoList = connect(mapStateToProps, mapDispatchToProps)(TodoList);
export default VisibleTodoList
connect 源码 返回一个包着渲染组件的高阶组件
- 将组件(WrappedComponent)传入
- 将
statePropsdispatchPropsthis.props传入到组件中,这样组件就可以用到 redux 中的数据
export const connect = (mapStateToProps, mapDispatchToProps) => (WrappedComponent) => {
class Connect extends Component {
static contextTypes = {
store: PropTypes.object
}
constructor () {
super()
this.state = {
allProps: {}
}
}
componentWillMount () {
const { store } = this.context
this._updateProps()
store.subscribe(() => this._updateProps())
}
_updateProps () {
const { store } = this.context
let stateProps = mapStateToProps
? mapStateToProps(store.getState(), this.props)
: {} // 防止 mapStateToProps 没有传入
let dispatchProps = mapDispatchToProps
? mapDispatchToProps(store.dispatch, this.props)
: {} // 防止 mapDispatchToProps 没有传入
this.setState({
allProps: {
...stateProps,
...dispatchProps,
...this.props
}
})
}
render () {
return <WrappedComponent {...this.state.allProps} />
}
}
return Connect
源码基础
1、从context里获取store
2、在componentWillMount 里通过mapStateToProps获取stateProp的值
3、 在componentWillMount 里通过mapDispatchToProps获取dispatchProps的值
4、在componentWillMount里订阅store的变化
5、将获得的stateProp,dispatchProps,还有自身的props合成一个props传给下面的组件
什么是React Render Props
跟 HOC 类似,
- 核心思想:将 class 组件中的 state 作为 props 通过函数方式传递给纯函数组件
class Factory extends React.Component {
constructor() {
this.state = {
/* state 即多个组件的公共逻辑的数据 */
}
}
render () {
return <div>{this.props.render(this.state)}</div>
}
}
// 调用
const App = () => {
<Factory
// render 是一个函数组件
render={(props) => <p>{props.a} {props.b} ...</p> }/>
}
实例:

对比 HOC 和 Render Props
- HOC: 模式简单,但会增加组件层级,容易出现 props 漏传的情况
- Render Props: 代码简洁
Redux
基本知识点
- 与 Vuex 作用相同 比 Vuex 学习成本高
- 不可变值,纯函数
- 单向数据流
- react-redux
- 异步 action
- 中间件
Redux 单向數據流
概念
- Action
- View
- Reducer
- Reducer 是一个纯函数,输入旧 State 和 Action,输出新 State
- Store
全局唯一的 Store 对象。大体可以认为是 MVC (Model、View、Control) 中的 Model。
流程
- dispatch action
- reducer -> newState 不可变值
- subscribe 触发通知,触发视图层刷新



react-redux
- Provider
- connect
- mapStateToProps
- mapDispatchToProps
React action 如何处理异步
// 同步 action
export const addTodo = text => {
// 返回 action
return {
type: 'ADD_TODO',
payload: text
}
}
// 异步 action
export const addTodoAsync = text => {
// 返回函数,其中有 dispatch 函数
return (dispatch) => {
fetch(url).then(res => {
// 异步执行 action
dispatch(addTodo(res.text))
})
}
}
- 一个中间件,提供
Redux异步解决方案
to enable Redux Thunk, use applyMiddleware():
import { createStore, applyMiddleware } from 'redux'
import thunk from 'redux-thunk'
import rootReducer from './reducers/index'
const store = createStore(rootReducer, applyMiddleware(thunk))
同类的有
- redux-promise
- redux-saga
简述 Redux 中间件原理
在 redux 的流程中,改造 dispatch 实现中间件的逻辑
实现代码
// 中间件使用
import { createStore } from 'redux'
import thunk from 'redux-thunk'
const logger = createLogger()
const store = createStore(
reducer,
applyMiddleware(thunk, logger)// 会按顺序执行
)
// logger 代码实现
let next = store.dispach;
store.dispatch = function dispatchAndLog(action) {
console.log('before action: ', action)
next(action)
console.log('after action: ', store.getState())
}
react-router 知识点
- hash模式(默认),eg: http://abc.com/#/user/001
- H5 history模式,eg: http://abc.com/user/001
- 需要server支持,因此无特殊需求选择hash模式
代码实现
import React from 'react'
// hash
import { HashRouter as Router, Switch, Route} from 'react-router-dom'
// H5
import { BrowserRouter as Router, Switch, Route} from 'react-router-dom'
function RouterComponent() {
return (
<Router>
<Switch>
<Router exact path="/">
<Home>
</Router>
<Router path="/project/:id">
<Project />
</Router>
<Router path="*">
<NotFound />
</Router>
</Switch>
</Router>
)
}
import React form 'react'
import { Link, useParams } from 'react-router-dom'
function Project() {
const { id } = useParams()
return (
<Link to="/">首页</Link>
)
}
React-router 路由跳转
import { useHistory } from 'react-router-dom'
function Trash() {
let history = useHistory();
return (<Button onClick={() => history.push('/')}>回到首页</Button>)
React-router 路由配置 懒加载
- Suspense
- lazy import
import React, { Suspense, lazy } from 'react'
const Home = lazy(() => import('../Home'))
const App = () => {
return <Router>
<Suspense>
<Switch>
<Router exact path="/" component={Home}>
</Switch>
</Suspense>
</Router>
}
react router 原理
参考资料
React 原理
函数式编程 不可变值
vdom 和 diff
JSX的本质
JSX是一种类XML语言,是JavaScript的语法扩展,全称是JavaScript XML。 实质是一个 js 对象
- JSX 相當於 Vue 模板
- Vue 模板不是 html
- JSX 不是 JS
核心
- React.createElement 即 h 函数,返回 vnode
- 第一个参数,可能是组件、html、tag
- 组件名,首字母必须大写(React 规定)
可以通过 babel 编译 JSX,看编译出来的本质
eg:
const ImgElem = <div id="div1">
<p>some text</p>
<img src={imgUrl}/>
</div>
const app = <div>
<ImgElem/>
</div>
// 结果
"use strict";
const ImgElem = /*#__PURE__*/React.createElement("div", {
id: "div1"
}, /*#__PURE__*/React.createElement("p", null, "some text"), /*#__PURE__*/React.createElement("img", {
src: imgUrl
}));
const app = /*#__PURE__*/React.createElement("div", null, /*#__PURE__*/React.createElement(ImgElem, null));
React.createElement(
type,
[props],
[...children]
)
Create and return a new React element of the given type.
React.createElement('div', null, [child1, child2, child3])
React.createElement('div', {...}, child1, child2, child3)
React.createElement(List, null, child1, child2, '文本节点')
h 函數
返回 vnode(virtual node 虚拟节点)的数据结构
patch 函數
合成事件机制
react 对原生事件封装了一层,统一接口,抹平了浏览器之间的差异。
setState 主流程 batchUpdate机制
transaction(事务)机制
组件渲染和更新过程
React fiber
- 是React16.13 新增的一个性能优化的方案
- React 内部运行机制,开发者体会不到
背景
更新性能问题
- JS 单线程,与 DOM 渲染共用同一线程;
- 当组件复杂,更新时计算和渲染压力大;
- 此时再有频繁的 DOM 操作需求(动画,鼠标拖拽),将掉帧卡顿
为了解决上述性能问题,更新阶段 patch 分为两个阶段
- reconciliation阶段: 执行 diff 算法,纯 JS 计算
- commit 阶段:将 diff 结果渲染到 DOM 上
解决方案fiber,任务分片 时间分片思想
- 将 reconciliation 阶段进行任务拆分 (因为 commit 阶段无法拆分)
- DOM 需要渲染时暂停,空闲时恢复
- 通过~~ window.requestIdleCallback~~(idle: 闲置) 可以知道 DOM 什么时候需要渲染
- window.requestIdleCallback()方法插入一个函数,这个函数将在浏览器空闲时期被调用
- 如果浏览器不支持该API,fiber 不生效,也无妨
- 早期的
React版本上确实是这么做的,但使用requestIdleCallback实际上有一些限制,执行频次不足,以致于无法实现流畅的 UI 渲染,扩展性差。- 因此
React团队放弃了requestIdleCallback用法,实现了自定义的版本。 - 使用
requestAnimationFrame + postMessage实现的。那为什么不用setTimeOut, 因为setTimeout即使为0 也会有一个最小毫秒延迟4ms,所以是用了postMessage,react18又换成了MessageChannel实现了队列方式去执行任务。
- 因此
学习
requestIdleCallback: RequestIdleCallback 实验
数据结构
- 由于原有的堆栈结构,暂停重启任务来实现分片,递归无法中断
- 实现分片需要
随时停止遍历并稍后恢复
- 实现分片需要
- 所以由
栈修改为单链表的形式,虚拟堆栈,在遍历树的时候不会增长堆栈
React-fiber如何优化性能
自定义 hooks
通过 antd 中的 Grid 判断是否是 desktop 版
// 定义
import { useState, useEffect } from 'react';
import { Grid } from 'antd';
const { useBreakpoint } = Grid;
export function useIsDesktop() {
const [isDesktop, setIsDesktop] = useState(true);
const screens = useBreakpoint();
useEffect(() => {
console.log('useBreakpoint screens :>> ', Object.entries(screens));
let isDesktop = Object.entries(screens).filter((screen) => {
console.log(!!screen[1]);
return !!screen[1];
}).some(screen => (screen[0] === 'md'))// 如果存在 md 尺寸, 證明是桌面版
console.log('isDesktop :>> ', isDesktop);
setIsDesktop(isDesktop)
});
return isDesktop;
}
// 使用
import { useIsDesktop } from '@/common/customHooks/useIsDesktop'
const isDesktop = useIsDesktop();
React Hooks
为什么有 React Hooks?解决了什么问题?
- React Hooks 是一系列函数
- 通过组合方式,实现 class 的继承。组合优于继承,没有上下文 this 的调用,代码更简洁易维护
有哪些 React Hooks?
- 基础
- useState
- useEffect
- useContext
- 高阶
- useMemo 和 useCallback
- useLayoutEffect
- useReducer
- 基础
如何自定义 Hooks
- 定义函数,然后导出
React Hooks 模拟生命周期
React Hooks 性能优化
useMemo 和 useCallback
useMemo 用于避免不必要的重复计算
useCallback 用于缓存函数,依赖没有变,返回缓存的函数的引用。该函数传入到子组件时,不会触发子组件的重新渲染
const inputchange = useCallback((e) => {
console.log('inputchange', e);
setInputValue(e.target.value);
}, []);
return <Child onChange={inputchange}/>
React.lazy() 和 Suspense 实现异步加载,Suspense 占据页面位置,lazy 原理是通过 ES6 中的 import 实现懒加载
React.memo()
使用 React Hooks 遇到什么坑
与 React Class 对比
出现 React Hooks 后,React 的开发生态有什么变化
路由、数据管理、表单数据都有 Hooks 版本的写法,统一写法,增加灵活性
路由
// 路由
import { useHistory, useParams } from 'react-router-dom'
const history = useHistory();
const { id } = useParams();
history.goBack()- 数据管理
import { useSelector, useDispatch } from 'react-redux'
// 使用 useSelector 获取 Redux 状态
const count = useSelector((state) => state.count)
const dispatch = useDispatch()
return (<div>
<button onClick={() => dispatch({ type: 'INCREMENT' })}>
Increment
</button>
</div>)// 代替 高阶函数 connect 连接组件:connect(mapStateToProps, mapDispatchToProps)(Counter)
const mapStateToProps = (state) => ({
count: state.count,
});
const mapDispatchToProps = (dispatch) => ({
increment: () => dispatch({ type: 'INCREMENT' }),
});- React Query(数据获取库)的变化:通过 useQuery 和 useMutation 来修改数据请求、缓存和同步
import { useQuery, QueryClient, QueryClientProvider } from 'react-query';
// 创建 QueryClient 实例
const queryClient = new QueryClient();
function fetchUsers() {
return fetch('<https://example.com/users').then((res)> => res.json());
}
function Users() {
const { data, error, isLoading } = useQuery('users', fetchUsers); // 使用 useQuery
if (isLoading) return <p>Loading...</p>;
if (error) return <p>Error: {error.message}</p>;
return (
<ul>
{data.map((user) => (
<li key={user.id}>{user.name}</li>
))}
</ul>
);
}
function App() {
return (
<QueryClientProvider client={queryClient}>
<Users />
</QueryClientProvider>
);
}
export default App;Formik(表单管理库)的变化
Formik 是一个流行的 React 表单管理库,它在 Hooks 引入后增加了 useFormik 钩子,用于在函数组件中更方便地管理表单状态和提交。
Formik 示例
import React from 'react';
import { useFormik } from 'formik';
function MyForm() {
const formik = useFormik({
initialValues: { name: '' },
onSubmit: (values) => {
console.log('Form Values:', values);
},
});
return (
<form onSubmit={formik.handleSubmit}>
<input
name="name"
onChange={formik.handleChange}
value={formik.values.name}
/>
<button type="submit">Submit</button>
</form>
);
}
export default MyForm;- 变化:在 Hooks 版本中,useFormik 替代了传统的 render props 模式,让表单管理在函数组件中更加自然和流畅。
- 优势:减少了代码的嵌套,提高了代码的可读性和表单管理的灵活性。
React Testing Library 的变化
React 18 新功能
- 并发性: 用 Transition API 进行并发控制
// before React 18
setInputValue(input)
setSearchQeury(input)
// React 18
setInputValue(input)
setTransition(() => {
setSearchQeury(input)
})
- 函数调用和事件的自动批处理,以提高应用内的性能
批处理演示
在React 18之前: https://codesandbox.io/s/hopeful-fire-ge4t2?file=/src/App.tsx
在React 18之后: https://codesandbox.io/s/morning-sun-lgz88?file=/src/index.js
- 用 Suspense API 为 SSR 提供更快的页面加载
<Suspense fallback={<Spinner />}>
{children}
</Suspense>

