1
问:老版本的 React 中,为什么写 jsx 的文件要默认引入 React?
如下
import React from 'react'
function Index(){
return <div>hello,world</div>
}
答:因为 jsx 在被 babel 编译后,写的 jsx 会变成上述 React.createElement 形式,所以需要引入 React,防止找不到 React 引起报错。
2
问: React.createElement 和 React.cloneElement 到底有什么区别呢?
答: 可以完全理解为,一个是用来创建 element 。另一个是用来修改 element,并返回一个新的 React.element 对象。
3
注意:不要尝试给函数组件 prototype 绑定属性或方法,即使绑定了也没有任何作用
,因为通过上面源码中 React 对函数组件的调用,是采用直接执行函数的方式,而不是通过new的方式。
那么,函数组件和类组件本质的区别是什么呢?
对于类组件来说,底层只需要实例化一次,实例中保存了组件的 state 等状态。
对于每一次更新只需要调用 render 方法以及对应的生命周期就可以了。但是在
函数组件中,每一次更新都是一次新的函数执行,一次函数组件的更新,里面
的变量会重新声明。
4
export default class index extends React.Component{
state = { number:0 }
handleClick1= () => {
this.setState({ number:this.state.number + 1 },()=>{ console.log( 'callback1', this.state.number) })
console.log(this.state.number)
this.setState({ number:this.state.number + 1 },()=>{ console.log( 'callback2', this.state.number) })
console.log(this.state.number)
this.setState({ number:this.state.number + 1 },()=>{ console.log( 'callback3', this.state.number) })
console.log(this.state.number)
}
handleClick2= () => {
setTimeout(()=>{
this.setState({ number:this.state.number + 1 },()=>{ console.log( 'callback1', this.state.number) })
console.log(this.state.number)
this.setState({ number:this.state.number + 1 },()=>{ console.log( 'callback2', this.state.number) })
console.log(this.state.number)
this.setState({ number:this.state.number + 1 },()=>{ console.log( 'callback3', this.state.number) })
console.log(this.state.number)
})
}
render(){
return <div>
{ this.state.number }
<button onClick={ this.handleClick1 } >button1</button>
<button onClick={ this.handleClick2 } >button2</button>
</div>
}
}
点击button1打印:0, 0, 0, callback1 1 ,callback2 1 ,callback3 1
刷新,点击button2打印: callback1 1 , 1, callback2 2 , 2,callback3 3 , 3
那么,如何在如上异步环境下,继续开启批量更新模式呢?
React-Dom 中提供了批量更新方法 unstable_batchedUpdates,可以去手动批量更新
import ReactDOM from 'react-dom'
const { unstable_batchedUpdates } = ReactDOM
setTimeout(()=>{
unstable_batchedUpdates(()=>{
this.setState({ number:this.state.number + 1 })
console.log(this.state.number)
this.setState({ number:this.state.number + 1})
console.log(this.state.number)
this.setState({ number:this.state.number + 1 })
console.log(this.state.number)
})
})
打印: 0 , 0 , 0 , callback1 1 , callback2 1 ,callback3 1
5
更新优先级
handerClick=()=>{
setTimeout(()=>{
this.setState({ number: 1 })
})
this.setState({ number: 2 })
ReactDOM.flushSync(()=>{
this.setState({ number: 3 })
})
this.setState({ number: 4 })
}
render(){
console.log(this.state.number)
return ...
}
打印 3 4 1 ,相信不难理解为什么这么打印了。
首先 flushSync this.setState({ number: 3 })设定了一个高优先级的更新,所以 2 和 3 被批量更新到 3 ,所以 3 先被打印。
更新为 4。
最后更新 setTimeout 中的 number = 1。
flushSync补充说明:flushSync 在同步条件下,会合并之前的 setState | useState,
可以理解成,如果发现了 flushSync ,就会先执行更新,如果之前有未更新的 setState | useState ,
就会一起合并了,所以就解释了如上,2 和 3 被批量更新到 3 ,所以 3 先被打印。
综上所述, React 同一级别更新优先级关系是:
flushSync 中的 setState > 正常执行上下文中 setState > setTimeout ,Promise 中的 setState。
6
export default function Index(props){
const [ number , setNumber ] = React.useState(0)
/* 监听 number 变化 */
React.useEffect(()=>{
console.log('监听number变化,此时的number是: ' + number )
},[ number ])
const handerClick = ()=>{
/** 高优先级更新 **/
ReactDOM.flushSync(()=>{
setNumber(2)
})
/* 批量更新 */
setNumber(1)
/* 滞后更新 ,批量更新规则被打破 */
setTimeout(()=>{
setNumber(3)
})
}
console.log(number)
return <div>
<span> { number }</span>
<button onClick={ handerClick } >number++</button>
</div>
}
![](https://img.haomeiwen.com/i24769119/de06861133424125.png)
7
上述讲的批量更新和 flushSync ,在函数组件中,dispatch 更新效果和类组件是一样的,
但是 useState 有一点值得注意,就是当调用改变 state 的函数dispatch,在本次函数执行
上下文中,是获取不到最新的 state 值的,把上述demo 如下这么改:
const [ number , setNumber ] = React.useState(0)
const handleClick = ()=>{
ReactDOM.flushSync(()=>{
setNumber(2)
console.log(number)
})
setNumber(1)
console.log(number)
setTimeout(()=>{
setNumber(3)
console.log(number)
})
}
结果: 0 0 0
原因很简单,函数组件更新就是函数的执行,在函数一次执行过程中,函数内部所有变量
重新声明,所以改变的 state ,只有在下一次函数组件执行时才会被更新。所以在如上同
一个函数执行上下文中,number 一直为0,无论怎么打印,都拿不到最新的 state 。
|--------问与答---------|
类组件中的 setState 和函数组件中的 useState 有什么异同? 相同点:
首先从原理角度出发,setState和 useState 更新视图,底层都调用了 scheduleUpdateOnFiber 方法,
而且事件驱动情况下都有批量更新规则。
不同点
在不是 pureComponent 组件模式下, setState 不会浅比较两次 state 的值,只要调用 setState,在没有其他优化手段的前提下,就会执行更新。
但是 useState 中的 dispatchAction 会默认比较两次 state 是否相同,然后决定是否更新组件。
setState 有专门监听 state 变化的回调函数 callback,可以获取最新state;
但是在函数组件中,只能通过 useEffect 来执行 state 变化引起的副作用。
setState 在底层处理逻辑上主要是和老 state 进行合并处理,而 useState 更倾向于重新赋值。
|--------end---------|
8.ref
场景三:高阶组件转发
如果通过高阶组件包裹一个原始类组件,就会产生一个问题,如果高阶组件 HOC 没有处理 ref ,那么由于高阶组件本身会返回一个新组件,
所以当使用 HOC 包装后组件的时候,标记的 ref 会指向 HOC 返回的组件,而并不是 HOC 包裹的原始类组件,为了解决这个问题,
forwardRef 可以对 HOC 做一层处理。
function HOC(Component){
class Wrap extends React.Component{
render(){
const { forwardedRef ,...otherprops } = this.props
return <Component ref={forwardedRef} {...otherprops} />
}
}
return React.forwardRef((props,ref)=> <Wrap forwardedRef={ref} {...props} /> )
}
class Index extends React.Component{
render(){
return <div>hello,world</div>
}
}
const HocIndex = HOC(Index)
export default ()=>{
const node = useRef(null)
useEffect(()=>{
console.log(node.current) /* Index 组件实例 */
},[])
return <div><HocIndex ref={node} /></div>
}
经过 forwardRef 处理后的 HOC ,就可以正常访问到 Index 组件实例了。
2 ref实现组件通信
如果有种场景不想通过父组件 render 改变 props 的方式,来触发子组件的更新,也就是子组件通过 state 单独管理数据层,
针对这种情况父组件可以通过 ref 模式标记子组件实例,从而操纵子组件方法,这种情况通常发生在一些数据层托管的组件上,
比如 <Form/> 表单,经典案例可以参考 antd 里面的 form 表单,暴露出对外的 resetFields , setFieldsValue 等接口,
可以通过表单实例调用这些 API 。
① 类组件 ref
对于类组件可以通过 ref 直接获取组件实例,实现组件通信。
/* 子组件 */
class Son extends React.PureComponent{
state={
fatherMes:'',
sonMes:''
}
fatherSay=(fatherMes)=> this.setState({ fatherMes }) /* 提供给父组件的API */
render(){
const { fatherMes, sonMes } = this.state
return <div className="sonbox" >
<div className="title" >子组件</div>
<p>父组件对我说:{ fatherMes }</p>
<div className="label" >对父组件说</div> <input onChange={(e)=>this.setState({ sonMes:e.target.value })}/>
<button className="searchbtn" onClick={ ()=> this.props.toFather(sonMes) } >to father</button>
</div>
}
}
/* 父组件 */
export default function Father(){
const [ sonMes , setSonMes ] = React.useState('')
const sonInstance = React.useRef(null) /* 用来获取子组件实例 */
const [ fatherMes , setFatherMes ] = React.useState('')
const toSon =()=> sonInstance.current.fatherSay(fatherMes) /* 调用子组件实例方法,改变子组件state */
return <div className="box" >
<div className="title" >父组件</div>
<p>子组件对我说:{ sonMes }</p>
<div className="label" >对子组件说</div> <input onChange={ (e) => setFatherMes(e.target.value) } className="input" />
<button className="searchbtn" onClick={toSon} >to son</button>
<Son ref={sonInstance} toFather={setSonMes} />
</div>
}
流程分析:
1 子组件暴露方法 fatherSay 供父组件使用,父组件通过调用方法可以设置子组件展示内容。
2 父组件提供给子组件 toFather,子组件调用,改变父组件展示内容,实现父 <-> 子 双向通信。
9 context
|--------问与答---------|
问:context 与 props 和 react-redux 的对比?
答: context解决了:
解决了 props 需要每一层都手动添加 props 的缺陷。
解决了改变 value ,组件全部重新渲染的缺陷。
react-redux 就是通过 Provider 模式把 redux 中的 store 注入到组件中的。
|--------end---------|
11 .优化篇-渲染控制
React 提供了几种控制 render 的方式。我这里会介绍原理和使用。说到对render 的控制,究其本质,主要有以下两种方式:
第一种就是从父组件直接隔断子组件的渲染,经典的就是 memo,缓存 element 对象。
第二种就是组件从自身来控制是否 render ,比如:PureComponent ,shouldComponentUpdate 。
memo 主要逻辑是
通过 memo 第二个参数,判断是否执行更新,如果没有那么第二个参数,那么以浅比较 props 为 diff 规则。如果相等,当前 fiber 完成工作,停止向下调和节点,所以被包裹的组件即将不更新。
memo 可以理解为包了一层的高阶组件,它的阻断更新机制,是通过控制下一级 children ,也就是 memo 包装的组件,是否继续调和渲染,来达到目的的。
接下来做一个小案例,利用 memo 做到自定义 props 渲染。 规则: 控制 props 中的 number 。
1 只有 number 更改,组件渲染。
2 只有 number 小于 5 ,组件渲染。
function TextMemo(props){ / /子组件
console.log('子组件渲染')
return <div>hello,world</div>
}
const controlIsRender = (pre,next)=>{
return ( pre.number === next.number ) || (pre.number !== next.number && next.number > 5) // number不改变或number 改变但值大于5->不渲染组件 | 否则渲染组件
}
const NewTexMemo = memo(TextMemo,controlIsRender)
class Index extends React.Component{
constructor(props){
super(props)
this.state={
number:1,
num:1
}
}
render(){
const { num , number } = this.state
return <div>
<div>
改变num:当前值 { num }
<button onClick={ ()=>this.setState({ num:num + 1 }) } >num++</button>
<button onClick={ ()=>this.setState({ num:num - 1 }) } >num--</button>
</div>
<div>
改变number: 当前值 { number }
<button onClick={ ()=>this.setState({ number:number + 1 }) } > number ++</button>
<button onClick={ ()=>this.setState({ number:number - 1 }) } > number -- </button>
</div>
<NewTexMemo num={ num } number={number} />
</div>
}
}
![](https://img.haomeiwen.com/i24769119/1a020c02be03d0a6.png)
![](https://img.haomeiwen.com/i24769119/8c9fdd79d5b891aa.png)
12. 优化篇-渲染调优
通过本章节,你将学会 Suspense 用法和原理,React.lazy 用法和配合 Suspense 实现代码分割,渲染错误边界、渲染异常的处理手段, 以及 diff 流程以及 key 的合理使用。
// 子组件
function UserInfo() {
// 获取用户数据信息,然后再渲染组件。
const user = getUserInfo();
return <h1>{user.name}</h1>;
}
// 父组件
export default function Index(){
return <Suspense fallback={<h1>Loading...</h1>}>
<UserInfo/>
</Suspense>
}
const LazyComponent = React.lazy(() => import('./test.js'))
export default function Index(){
return <Suspense fallback={<div>loading...</div>} >
<LazyComponent />
</Suspense>
}
class Index extends React.Component{
state={
hasError:false
}
componentDidCatch(...arg){
uploadErrorLog(arg) /* 上传错误日志 */
this.setState({ /* 降级UI */
hasError:true
})
}
render(){
const { hasError } =this.state
return <div>
{ hasError ? <div>组件出现错误</div> : <ErrorTest /> }
<div> hello, my name is alien! </div>
<Test />
</div>
}
}
class Index extends React.Component{
state={
hasError:false
}
static getDerivedStateFromError(){
return { hasError:true }
}
render(){
/* 如上 */
}
}
/**
*
* @param {*} Component 需要异步数据的component
* @param {*} api 请求数据接口,返回Promise,可以再then中获取与后端交互的数据
* @returns
*/
function AysncComponent(Component,api){
const AysncComponentPromise = () => new Promise(async (resolve)=>{
const data = await api()
resolve({
default: (props) => <Component rdata={data} { ...props} />
})
})
return React.lazy(AysncComponentPromise)
}
14. 优化篇-细节处理(持续)
1 React中防抖和节流
2 按需引入
.babelrc 增加对 antd 样式按需引入。
["import", {
"libraryName":
"antd",
"libraryDirectory": "es",
"style": true
}]
瘦身后体积:
3 React动画
① 首选:动态添加类名
② 其次:操纵原生 DOM
③ 再者:setState + css3
网友评论