生命周期
-
挂载:
constructorcomponentWillMount()getDerivedStateFromProps(nextProps, prevState):挂载阶段有,更新阶段也有componentDidMount()
-
更新:
componentWillReceiveProps(nextProps)=getDerivedStateFromProps(nextProps, prevState)
componentWillReceiveProps (nextProps) { nextProps.openNotice !== this.props.openNotice&&this.setState({ openNotice:nextProps.openNotice },() => { console.log(this.state.openNotice:nextProps) //将state更新为nextProps,在setState的第二个参数(回调)可以打 印出新的state }) }static getDerivedStateFromProps(nextProps, prevState) { if (nextProps.isLogin !== prevState.isLogin) { return { isLogin: nextProps.isLogin, }; } return null; } componentDidUpdate(prevProps, prevState) { if (!prevState.isLogin && this.props.isLogin) { this.handleClose(); } }shouldComponentUpdate(nextProps,nextState)componentWillUpdate(nextProps,nextState)=getSnapshotBeforeUpdate(prevProps, prevState)componentDidUpdate(prevProps,prevState)
-
卸载:
componentWillUnMount()
setState
图片来源:React 渲染优化:diff 与 shouldComponentUpdate
- 不可变值
- 不能直接修改this.state里的值,不会触发render更新
- 可能是异步更新
// 第三,setState 可能是异步更新(有可能是同步更新) ----------------------------
this.setState({
count: this.state.count + 1
}, () => {
// 联想 Vue $nextTick - DOM
console.log('count by callback', this.state.count) // 回调函数中可以拿到最新的 state
})
console.log('count', this.state.count) // 异步的,拿不到最新值
// // setTimeout 中 setState 是同步的
setTimeout(() => {
this.setState({
count: this.state.count + 1
})
console.log('count in setTimeout', this.state.count)
}, 0)
// 自己定义的 DOM 事件,setState 是同步的。在 componentDidMount 中
componentDidMount() {
// 自己定义的 DOM 事件,setState 是同步的
document.body.addEventListener('click', this.bodyClickHandler)
}
componentWillUnmount() {
// 及时销毁自定义 DOM 事件
document.body.removeEventListener('click', this.bodyClickHandler)
// clearTimeout
}
- 可能会被合并
// 第四,state 异步更新的话,更新前会被合并 ----------------------------
// // 传入对象,会被合并(类似 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.count + 1
}
})
this.setState((prevState, props) => {
return {
count: prevState.count + 1
}
})
this.setState((prevState, props) => {
return {
count: prevState.count + 1
}
})
}
-
函数组件默认没有state
-
React 组件如何通讯
React组件中的this
React事件
- bind this
- 关于event参数
- 传递自定义参数
// 获取 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) // 指向 document !!!
// 1. event 是 SyntheticEvent ,模拟出来 DOM 事件所有能力
// 2. event.nativeEvent 是原生事件对象
// 3. 所有的事件,都被挂载到 document 上
// 4. 和 DOM 事件不一样,和 Vue 事件也不一样
}
React 表单
- 受控组件 组件中的值受this.state里控制:
- input textarea select用value
- checkbox radio 用 checked
props
-
props传递数据
-
props传递函数
-
props 类型检查
-
JSX本质是什么
-
context是什么,有何用途?
-
shouldComponentUpdate
-
描述redux单项数据流
-
setState是同步还是异步
高级特新
函数组件
- 纯函数,输入props,输出JSX
- 没有实例,没有生命周期,没有state
- 不能扩展其他方法
非受控组件
组件的值不是由this.state控制
使用场景:
-
必须手动操作DOM元素,setState实现不了
-
文件上床
-
某些富文本编辑器,需要传入DOM元素
-
ref:
constructor(props) {
super(props)
this.state = {}
this.nameInputRef = React.createRef() // 创建 ref
}
class App extends React.Component {
constructor(props) {
super(props)
this.state = {
name: '双越',
flag: true,
}
this.nameInputRef = React.createRef() // 创建 ref
}
render() {
// input defaultValue
return <div>
{/* 使用 defaultValue 而不是 value ,使用 ref */}
<input defaultValue={this.state.name} ref={this.nameInputRef}/>
{/* state 并不会随着改变 */}
<span>state.name: {this.state.name}</span>
<br/>
<button onClick={this.alertName}>alert name</button>
</div>
// checkbox defaultChecked
return <div>
<input
type="checkbox"
defaultChecked={this.state.flag}
/>
</div>
// file
return <div>
<input type="file" ref={this.fileInputRef}/>
<button onClick={this.alertFile}>alert file</button>
</div>
}
alertName = () => {
const elem = this.nameInputRef.current // 通过 ref 获取 DOM 节点
alert(elem.value) // 不是 this.state.name
}
alertFile = () => {
const elem = this.fileInputRef.current // 通过 ref 获取 DOM 节点
alert(elem.files[0].name)
}
}
- defaultValue、defaulChecked
- 手动操作DOM元素
Portals
让组件渲染到父组件以外
使用场景:
- overflow: hidden
- 父组件z-index值太小
- fixed需要放在body第一层级
render() {
// // 正常渲染
// return <div className="modal">
// {this.props.children} {/* vue slot */}
// </div>
// 使用 Portals 渲染到 body 上。
// fixed 元素要放在 body 上,有更好的浏览器兼容性。
return ReactDOM.createPortal(
<div className="modal">{this.props.children}</div>,
document.body // DOM 节点
)
}
context
应用场景:
- 公共信息(语言、主题)如何传递给每个组件
- 用props太繁琐
- 用redux 小题大做
生产数据、消费数据(class组件和函数组件使用方式不一样)
import React from 'react'
// 创建 Context 填入默认值(任何一个 js 变量)
const ThemeContext = React.createContext('light')
// 底层组件 - 函数是组件
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。
// 中间的组件再也不必指明往下传递 theme 了。
function Toolbar(props) {
return (
<div>
<ThemedButton />
<ThemeLink />
</div>
)
}
class App extends React.Component {
constructor(props) {
super(props)
this.state = {
theme: 'light'
}
}
render() {
return <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()
- React.lazy
- React.Suspense
const ContextDemo = React.lazy(() => import('./ContextDemo'))
<React.Suspense fallback={<div>Loading...</div>}>
<ContextDemo/>
</React.Suspense>
性能优化
性能优化对React更重要
-
shouldComponentUpdate React默认:父组件有更新,子组件则无条件也更新
-
PureComponent和React.memo
- PureComponent和React,SCU中实现了浅比较
- memo,函数组件中的PureComponent
- 浅比较已使用大部分情况(尽量不做深比较)
-
不可变值
immutable.js- 彻底拥抱不可变值
- 基于共享数据(不是深拷贝)
const map1 = Immutable.Map({a: 1, b: 2, c: 3})
const map2 = map1.set('b', 50)
map1.get('b') //2
map2.get('b') //50
// 增加 shouldComponentUpdate
shouldComponentUpdate(nextProps, nextState) {
// _.isEqual 做对象或者数组的深度比较(一次性递归到底)
if (_.isEqual(nextProps.list, this.props.list)) {
// 相等,则不重复渲染
return false
}
return true // 不相等,则渲染
}
React高阶组件
- mixin,已被React弃用
- 高阶组件HOC 设计思想,传入一个组件,传出一个组件
// 高阶组件
const withMouse = (Component) => {
class withMouseComponent extends React.Component {
constructor(props) {
super(props)
this.state = { x: 0, y: 0 }
}
handleMouseMove = (event) => {
this.setState({
x: event.clientX,
y: event.clientY
})
}
render() {
return (
<div style={{ height: '500px' }} onMouseMove={this.handleMouseMove}>
{/* 1. 透传所有 props 2. 增加 mouse 属性 */}
<Component {...this.props} mouse={this.state}/>
</div>
)
}
}
return withMouseComponent
}
const App = (props) => {
const a = props.a
const { x, y } = props.mouse // 接收 mouse 属性
return (
<div style={{ height: '500px' }}>
<h1>The mouse position is ({x}, {y})</h1>
<p>{a}</p>
</div>
)
}
export default withMouse(App) // 返回高阶函数
- Render Props 核心思想:通过一个函数将class组件的state作为props传递给纯函数组件
class Factory extends React.Component {
constructor() {
this.state = {
/*state即多个组件的公共逻辑的数据*/
}
}
/*修改 state*/
render(){
return <div>{this.props.render(this.state)}</div>
}
}
const App = () => (
<Factory render={
/* render 是一个函数组件*/
(props) => <p>{props.a} {props.b} ..</p>
}/>
)
Redux
基本概念
- store state
- action
- reducer
dispatch(action) reducer -> newState subscribe(订阅)
单项数据流
react-redux
异步action
//同步 action
export const addTodo = text => {
//返回action对象
return {
type: 'ADD_TODO',
id: nextTodoId++,
text
}
}
//异步 action
export const addTodoAsync = text => {
//返回函数,其中有dispatch参数
return (dispatch) => {
//ajax 异步获取数据
fetch(url).then(res => {
//执行异步action
dispatch(addTodo(res.text))
})
}
}
中间件
- redux-thunk
- redux-promise
- redux-saga
React-router
路由模式:hash、H5 history
- hash模式(默认),如abc.com/#/user/10
- H5 history模式,如 abc.com/user/20(需se…
路由配置:动态路由、懒加载
React 原理
函数式编程
-
一种编程范式,概念比较多
-
纯函数
-
不可变值
vdom和diff
- h函数
- vnode数据结构
- patch函数
- 只比较同一层级,不跨级比较
- tag不相同,则直接删除重建,不再深度比较
- tag和key,两者都相同,则认为是相同节点,不再深度比较
{
tag: 'div',
props: {
className: 'container',
id: 'div1'
}
children: [
{tag: 'p', children: 'vdom'},
{tag: 'ul', props: { style: 'font-size: 20px'}, children: [{tag: 'li', children: 'a'}]},
]
}
JSX本质
- React.createElement即h函数,返回
vnode - 第一个参数,可能是组件,也可能是html tag
- 组件名,首字母必须大写(Rect规定)
合成事件
- 所有事件都挂载到document上
- event不是原生的,是SyntheticEvent合成事件对象
- 和Vue事件不同,和DOM事件也不同
作用:
- 更好的兼容性和跨平台
- 挂载到document上,减少内存消耗,避免频繁解绑
- 方便事件的统一管理(如事物机制)
setState batchUpdate
- 有时异步(普通使用),有时同步(setTimeout、自定义DOM事件:addEventListener)
- 有时合并(对象形式),有时不合并(函数形式)【后者比较好理解(像Object.assign),主要讲解前者】
setState主流程
batchUpdate机制
- setState无所谓异步还是同步
- 看是否能命中batchUpdate机制
- 判断isBatchingUpdates
命中batchUpdate机制:
- 生命周期(和它调用的函数)
- React中注册的事件(和它调用的函数)
- React可以管理的入口
不能命中batchUpdate机制:
- setTimeout setInterval等(和它调用的函数)
- 自定义的DOM事件(和它调用的函数)
transaction(事务)机制
组件渲染过程
- props state
- render()生成vnode
- patch(elem,vnode)
JSX如何渲染为页面
- JSX即createElement函数
- 执行生成vnode
- patch(elem, vnode)和patch(vnode,newVnode)
setState之后如何更新页面
- setState(newState) --> dirtyComponents(可能有子组件)
- render()生成newVnode
- patch(vnode,newVnode)
fiber
patch被拆分为两个阶段:
- reconciliation阶段-执行diff算法,纯JS计算
- commit阶段-将diff结果渲染成DOM
存在的性能问题
- JS是单线程,且和DOM渲染共用一个线程
- 当组件足够复杂,组件更新时计算和渲染都压力大
- 同时再有DOM操作需求(动画,鼠标拖拽等),将卡顿
解决方案fiber
React内部运行机制
- 将reconciliation阶段进行任务拆分(commit无法拆分)
- DOM需要渲染时暂停,空闲时恢复
- window.requestIdleCallback
面试题
组件之间如何通讯
- 父子组件props
- 自定义事件
- Redux和Context
JSX本质
- createElement
- 执行返回vnode
Context是什么,如何应用?
- 父组件,向其下所有子孙组件传递信息
- 如一些简单的公共信息:主题色、语言等
- 复杂的公共信息,使用redux
shouldComponentUpdate用途
- 性能优化
- 配合“不可变值”一起使用,否则会出错
redux 单项数据流
setState 场景题
什么是纯函数
- 返回一个新值,没有副作用,不会修改原值
- 重点:不可变值
- arr1 = arr.slice()
React组件生命周期
- 单组件生命周期
- 父子组件生命周期
- 注意SCU
React发起ajax应该在哪个生命周期
- componentDidMount
渲染列表,为何使用key
- 必须使用key,且不能是index和random
- diff算法中通过tag和key来判断,是否是sanmeNode
- 减少渲染次数,提升渲染性能
函数组件和class组件区别
- 纯函数,输入props,输出JSX
- 没有实例,没有生命周期,没有state
- 不能扩展其他方法
什么是受控组件
- 表单的值,受state控制
- 需要自行监听onChange,更新state
- 对比非受控组件
何时使用异步组件
- 加载大组件
- 路由懒加载
多个组件有公共逻辑,如何抽离
- 高阶组件
- Render Props
- mixin 已被React废弃
redux如何进行异步请求
- 使用异步action
- 如 redux-thunk
react-router如何配置懒加载
PureComponent有何区别
- 实现了浅比较的shouldComponentUpdate
- 优化性能
- 但要结合不可变值使用
React事件和DOM事件的区别
- 所有事件挂载到document上
- event不是原生的,是SyntheticEvent合成事件对象
- dispatchEvent
React性能优化
- 渲染列表时加key
- 自定义事件、DOM事件及时销毁
- 合理使用异步组件
- 减少函数bind this的次数
- 合理使用SCU PureComponent和memo
- 合理使用Immutable.js
- wepack层面的优化
- 前端通用的性能优化,如图片懒加载
- 使用SSR
React和Vue的区别
- 都支持组件化
- 都是数据驱动视图
- 都使用vdom操作dom
- React使用JSX拥抱JS,Vue使用模板拥抱html
- React函数式编程,Vue声明式编程
- React更多需要自力更生,Vue把想要的都给你