滴滴前端二面react面试题总结
React中refs的作用是什么?有哪些应用场景?
Refs 提供了一种方式,用于访问在 render 方法中创建的 React 元素或 DOM 节点。Refs 应该谨慎使用,如下场景使用 Refs 比较适合:
- 处理焦点、文本选择或者媒体的控制
- 触发必要的动画
- 集成第三方 DOM 库
Refs 是使用 React.createRef() 方法创建的,他通过 ref 属性附加到 React 元素上。 要在整个组件中使用 Refs,需要将 ref 在构造函数中分配给其实例属性:
class MyComponent extends React.Component {
constructor(props) {
super(props)
this.myRef = React.createRef()
}
render() {
return <div ref={this.myRef} />
}
}
React-Router如何获取URL的参数和历史对象?
(1)获取URL的参数
- get传值
路由配置还是普通的配置,如:'admin'
,传参方式如:'admin?id='1111''
。通过this.props.location.search
获取url获取到一个字符串'?id='1111'
可以用url,qs,querystring,浏览器提供的api URLSearchParams对象或者自己封装的方法去解析出id的值。
- 动态路由传值
路由需要配置成动态路由:如path='/admin/:id'
,传参方式,如'admin/111'
。通过this.props.match.params.id
取得url中的动态路由id部分的值,除此之外还可以通过useParams(Hooks)
来获取
- 通过query或state传值
传参方式如:在Link组件的to属性中可以传递对象{pathname:'/admin',query:'111',state:'111'};
。通过this.props.location.state
或this.props.location.query
来获取即可,传递的参数可以是对象、数组等,但是存在缺点就是只要刷新页面,参数就会丢失。
(2)获取历史对象
- 如果React >= 16.8 时可以使用 React Router中提供的Hooks
import { useHistory } from "react-router-dom";
let history = useHistory();
2.使用this.props.history获取历史对象
let history = this.props.history;
当调用 setState的时候,发生了什么操作?**
当调用 setState时, React做的第一件事是将传递给setState的对象合并到组件的当前状态,这将启动一个称为和解( reconciliation)的过程。和解的最终目标是,根据这个新的状态以最有效的方式更新DOM。为此, React将构建一个新的 React虚拟DOM树(可以将其视为页面DOM元素的对象表示方式)。一旦有了这个DOM树,为了弄清DOM是如何响应新的状态而改变的, React会将这个新树与上一个虚拟DOM树比较。这样做, React会知道发生的确切变化,并且通过了解发生的变化后,在绝对必要的情况下进行更新DOM,即可将因操作DOM而占用的空间最小化。
在React中组件的this.state和setState有什么区别?
this.state通常是用来初始化state的,this.setState是用来修改state值的。如果初始化了state之后再使用this.state,之前的state会被覆盖掉,如果使用this.setState,只会替换掉相应的state值。所以,如果想要修改state的值,就需要使用setState,而不能直接修改state,直接修改state之后页面是不会更新的。
用户不同权限 可以查看不同的页面 如何实现?
- Js方式 根据用户权限类型,把菜单配置成json, 没有权限的直接不显示
- react-router 方式 在route 标签上 添加onEnter事件,进入路由之前替换到首页
<Route path="/home" component={App} onEnter={(nexState,replace)=>{
if(nexState.location.pathname!=='/'){
var sid = UtilsMoudle.getSidFromUrl(nexState);
if(!sid){
replace("/")
}else{
console.log(sid);
}
}
}}>
- 自己封装一个privateRouter组件 里面判断是否有权限,有的话返回 <Route path={path} component={component} exact={exact}/> 没有权限的话component 返回一个提示信息的组件。
- 扩展一下,如果是根据用权限来判断是否隐藏组件该怎么做呢? react 可以使用高阶组件,在高阶组件里面判断是否有权限,然后判断是否返回组件,无权限返回null vue 可以使用自定义指令,如果没有权限移除组件
// 需要在入口处添加自定义权限指令v-auth,显示可操作组件
Vue.directive('auth', {
bind: function (el, binding, vnode) {
// 用户权限表
const rules = auths
for (let i = 0; i < rules.length; i++) {
const item = rules[i]
if(!binding.value || (binding.value == item.auth)){
// 权限允许则显示组件
return true
}
}
// 移除组件
el.parentNode.removeChild(el)
}
})
// 使用
<template>
<div>
<Button v-auth="admin_user_add">添加用户</Button>
<Button v-auth="admin_user_del">删除用户</Button>
<Button v-auth="admin_user_edit">编辑用户</Button>
</div>
</template>
react和vue的区别
相同点:
- 数据驱动页面,提供响应式的试图组件
- 都有virtual DOM,组件化的开发,通过props参数进行父子之间组件传递数据,都实现了webComponents规范
- 数据流动单向,都支持服务器的渲染SSR
- 都有支持native的方法,react有React native, vue有wexx
不同点:
- 数据绑定:Vue实现了双向的数据绑定,react数据流动是单向的
- 数据渲染:大规模的数据渲染,react更快
- 使用场景:React配合Redux架构适合大规模多人协作复杂项目,Vue适合小快的项目
- 开发风格:react推荐做法jsx + inline style把html和css都写在js了
vue是采用webpack +vue-loader单文件组件格式,html, js, css同一个文件
React.forwardRef是什么?它有什么作用?
React.forwardRef 会创建一个React组件,这个组件能够将其接受的 ref 属性转发到其组件树下的另一个组件中。这种技术并不常见,但在以下两种场景中特别有用:
- 转发 refs 到 DOM 组件
- 在高阶组件中转发 refs
React 的生命周期方法有哪些?
componentWillMount
:在渲染之前执行,用于根组件中的 App 级配置。componentDidMount
:在第一次渲染之后执行,可以在这里做AJAX请求,DOM 的操作或状态更新以及设置事件监听器。componentWillReceiveProps
:在初始化render
的时候不会执行,它会在组件接受到新的状态(Props)时被触发,一般用于父组件状态更新时子组件的重新渲染shouldComponentUpdate
:确定是否更新组件。默认情况下,它返回true
。如果确定在state
或props
更新后组件不需要在重新渲染,则可以返回false
,这是一个提高性能的方法。componentWillUpdate
:在shouldComponentUpdate
返回true
确定要更新组件之前件之前执行。componentDidUpdate
:它主要用于更新DOM以响应props
或state
更改。componentWillUnmount
:它用于取消任何的网络请求,或删除与组件关联的所有事件监听器。
React中什么是受控组件和非控组件?
(1)受控组件 在使用表单来收集用户输入时,例如<input><select><textearea>
等元素都要绑定一个change事件,当表单的状态发生变化,就会触发onChange事件,更新组件的state。这种组件在React中被称为受控组件,在受控组件中,组件渲染出的状态与它的value或checked属性相对应,react通过这种方式消除了组件的局部状态,使整个状态可控。react官方推荐使用受控表单组件。
受控组件更新state的流程:
- 可以通过初始state中设置表单的默认值
- 每当表单的值发生变化时,调用onChange事件处理器
- 事件处理器通过事件对象e拿到改变后的状态,并更新组件的state
- 一旦通过setState方法更新state,就会触发视图的重新渲染,完成表单组件的更新
受控组件缺陷: 表单元素的值都是由React组件进行管理,当有多个输入框,或者多个这种组件时,如果想同时获取到全部的值就必须每个都要编写事件处理函数,这会让代码看着很臃肿,所以为了解决这种情况,出现了非受控组件。
(2)非受控组件 如果一个表单组件没有value props(单选和复选按钮对应的是checked props)时,就可以称为非受控组件。在非受控组件中,可以使用一个ref来从DOM获得表单值。而不是为每个状态更新编写一个事件处理程序。
React官方的解释:
要编写一个非受控组件,而不是为每个状态更新都编写数据处理函数,你可以使用 ref来从 DOM 节点中获取表单数据。因为非受控组件将真实数据储存在 DOM 节点中,所以在使用非受控组件时,有时候反而更容易同时集成 React 和非 React 代码。如果你不介意代码美观性,并且希望快速编写代码,使用非受控组件往往可以减少你的代码量。否则,你应该使用受控组件。
例如,下面的代码在非受控组件中接收单个属性:
class NameForm extends React.Component {
constructor(props) {
super(props);
this.handleSubmit = this.handleSubmit.bind(this);
}
handleSubmit(event) {
alert('A name was submitted: ' + this.input.value);
event.preventDefault();
}
render() {
return (
<form onSubmit={this.handleSubmit}>
<label>
Name: <input type="text" ref={(input) => this.input = input} /> </label>
<input type="submit" value="Submit" />
</form>
);
}
}
总结: 页面中所有输入类的DOM如果是现用现取的称为非受控组件,而通过setState将输入的值维护到了state中,需要时再从state中取出,这里的数据就受到了state的控制,称为受控组件。
怎么用 React.createElement 重写下面的代码
Question:
const element = (
<h1 className="greeting">
Hello, rdhub.cn!
</h1>
);
Answer:
const element = React.createElement(
'h1',
{className: 'greeting'},
'Hello, rdhub.cn!'
);
高阶组件
高阶函数:如果一个函数接受一个或多个函数作为参数或者返回一个函数就可称之为高阶函数。
高阶组件:如果一个函数 接受一个或多个组件作为参数并且返回一个组件 就可称之为 高阶组件。
react 中的高阶组件
React 中的高阶组件主要有两种形式:属性代理和反向继承。
属性代理 Proxy
- 操作
props
- 抽离
state
- 通过
ref
访问到组件实例 - 用其他元素包裹传入的组件
WrappedComponent
反向继承
会发现其属性代理和反向继承的实现有些类似的地方,都是返回一个继承了某个父类的子类,只不过属性代理中继承的是 React.Component
,反向继承中继承的是传入的组件 WrappedComponent
。
反向继承可以用来做什么:
1.操作 state
高阶组件中可以读取、编辑和删除WrappedComponent
组件实例中的state
。甚至可以增加更多的state
项,但是非常不建议这么做因为这可能会导致state
难以维护及管理。
function withLogging(WrappedComponent) {
return class extends WrappedComponent {
render() {
return (
<div>;
<h2>;Debugger Component Logging...<h2>;
<p>;state:<p>;
<pre>;{JSON.stringify(this.state, null, 4)}<pre>;
<p>props:<p>;
<pre>{JSON.stringify(this.props, null, 4)}<pre>;
{super.render()}
<div>;
);
}
};
}
2.渲染劫持(Render Highjacking)
条件渲染通过 props.isLoading 这个条件来判断渲染哪个组件。
修改由 render() 输出的 React 元素树
在React中如何避免不必要的render?
React 基于虚拟 DOM 和高效 Diff 算法的完美配合,实现了对 DOM 最小粒度的更新。大多数情况下,React 对 DOM 的渲染效率足以业务日常。但在个别复杂业务场景下,性能问题依然会困扰我们。此时需要采取一些措施来提升运行性能,其很重要的一个方向,就是避免不必要的渲染(Render)。这里提下优化的点:
- shouldComponentUpdate 和 PureComponent
在 React 类组件中,可以利用 shouldComponentUpdate或者 PureComponent 来减少因父组件更新而触发子组件的 render,从而达到目的。shouldComponentUpdate 来决定是否组件是否重新渲染,如果不希望组件重新渲染,返回 false 即可。
- 利用高阶组件
在函数组件中,并没有 shouldComponentUpdate 这个生命周期,可以利用高阶组件,封装一个类似 PureComponet 的功能
- 使用 React.memo
React.memo 是 React 16.6 新的一个 API,用来缓存组件的渲染,避免不必要的更新,其实也是一个高阶组件,与 PureComponent 十分类似,但不同的是, React.memo只能用于函数组件。
对React实现原理的理解
简版
react
和vue
都是基于vdom
的前端框架,之所以用vdom
是因为可以精准的对比关心的属性,而且还可以跨平台渲染- 但是开发不会直接写
vdom
,而是通过jsx
这种接近html
语法的DSL
,编译产生render function
,执行后产生vdom
vdom
的渲染就是根据不同的类型来用不同的dom api
来操作dom
- 渲染组件的时候,如果是函数组件,就执行它拿到
vdom
。class
组件就创建实例然后调用render
方法拿到vdom
。vue
的那种option
对象的话,就调用render
方法拿到vdom
- 组件本质上就是对一段
vdom
产生逻辑的封装,函数
、class
、option
对象甚至其他形式都可以 react
和vue
最大的区别在状态管理方式上,vue
是通过响应式,react
是通过setState
的api
。我觉得这个是最大的区别,因为它导致了后面react
架构的变更react
的setState
的方式,导致它并不知道哪些组件变了,需要渲染整个vdom
才行。但是这样计算量又会比较大,会阻塞渲染,导致动画卡顿。所以react
后来改造成了fiber
架构,目标是可打断的计算- 为了这个目标,不能变对比变更新
dom
了,所以把渲染分为了render
和commit
两个阶段,render
阶段通过schedule
调度来进行reconcile
,也就是找到变化的部分,创建dom
,打上增删改的tag
,等全部计算完之后,commit
阶段一次性更新到dom
- 打断之后要找到父节点、兄弟节点,所以
vdom
也被改造成了fiber
的数据结构,有了parent
、sibling
的信息 - 所以
fiber
既指这种链表的数据结构,又指这个render
、commit
的流程 reconcile
阶段每次处理一个fiber
节点,处理前会判断下shouldYield
,如果有更高优先级的任务,那就先执行别的commit
阶段不用再次遍历fiber
树,为了优化,react
把有effectTag
的fiber
都放到了effectList
队列中,遍历更新即可- 在
dom
操作前,会异步调用useEffect
的回调函数,异步是因为不能阻塞渲染 - 在
dom
操作之后,会同步调用useLayoutEffect
的回调函数,并且更新ref
- 所以,
commit
阶段又分成了before mutation
、mutation
、layout
这三个小阶段,就对应上面说的那三部分
理解了vdom
、jsx
、组件本质
、fiber
、render(reconcile + schedule)
+commit(before mutation、mutation、layout)
的渲染流程,就算是对react
原理有一个比较深的理解
下面展开分析
vdom
为什么 react
和 vue
都要基于 vdom
呢?直接操作真实 dom
不行么?
考虑下这样的场景:
- 渲染就是用
dom api
对真实dom
做增删改,如果已经渲染了一个dom
,后来要更新,那就要遍历它所有的属性,重新设置,比如id
、clasName
、onclick
等。 - 而
dom
的属性是很多的:
- 有很多属性根本用不到,但在更新时却要跟着重新设置一遍。
- 能不能只对比我们关心的属性呢?
- 把这些单独摘出来用
JS
对象表示不就行了? - 这就是为什么要有
vdom
,是它的第一个好处。 - 而且有了
vdom
之后,就没有和dom
强绑定了,可以渲染到别的平台,比如native
、canvas
等等。 - 这是
vdom
的第二个好处。 - 我们知道了
vdom
就是用JS
对象表示最终渲染的dom
的,比如:
{
type: 'div',
props: {
id: 'aaa',
className: ['bbb', 'ccc'],
onClick: function() {}
},
children: []
}
然后用渲染器把它渲染出来,但是要让开发去写这样的 vdom
么?那肯定不行,这样太麻烦了,大家熟悉的是 html
那种方式,所以我们要引入编译的手段
dsl 的编译
dsl
是domain specific language
,领域特定语言的意思,html
、css
都是web
领域的dsl
- 直接写
vdom
太麻烦了,所以前端框架都会设计一套dsl
,然后编译成render function
,执行后产生vdom
。 vue
和react
都是这样
这套 dsl 怎么设计呢?前端领域大家熟悉的描述dom
的方式是html
,最好的方式自然是也设计成那样。所以vue
的template
,react
的jsx
就都是这么设计的。vue
的template compiler
是自己实现的,而react
的jsx
的编译器是babel
实现的,是两个团队合作的结果。
编译成 render function
后再执行就是我们需要的 vdom
。接下来渲染器把它渲染出来就行了。那渲染器怎么渲染 vdom
的呢?
渲染 vdom
渲染 vdom
也就是通过 dom api
增删改 dom
。比如一个 div
,那就要 document.createElement
创建元素,然后 setAttribute
设置属性,addEventListener
设置事件监听器。如果是文本,那就要 document.createTextNode
来创建。所以说根据 vdom
类型的不同,写个 if else
,分别做不同的处理就行了。没错,不管 vue
还是 react
,渲染器里这段 if else
是少不了的:
switch (vdom.tag) {
case HostComponent:
// 创建或更新 dom
case HostText:
// 创建或更新 dom
case FunctionComponent:
// 创建或更新 dom
case ClassComponent:
// 创建或更新 dom
}
react
里是通过tag
来区分vdom
类型的,比如HostComponent
就是元素,HostText
就是文本,FunctionComponent
、ClassComponent
就分别是函数组件和类组件。那么问题来了,组件怎么渲染呢?这就涉及到组件的原理了:
组件
我们的目标是通过vdom
描述界面,在react
里会使用jsx
。这样的jsx
有的时候是基于state
来动态生成的。如何把state
和jsx
关联起来呢?封装成function
、class
或者option
对象的形式。然后在渲染的时候执行它们拿到vdom
就行了。
这就是组件的实现原理:
switch (vdom.tag) {
case FunctionComponent:
const childVdom = vdom.type(props);
render(childVdom);
//...
case ClassComponent:
const instance = new vdom.type(props);
const childVdom = instance.render();
render(childVdom);
//...
}
如果是函数组件,那就传入 props
执行它,拿到 vdom
之后再递归渲染。如果是 class
组件,那就创建它的实例对象,调用 render
方法拿到 vdom
,然后递归渲染。所以,大家猜到 vue
的 option
对象的组件描述方式怎么渲染了么?
{
data: {},
props: {}
render(h) {
return h('div', {}, '');
}
}
没错,就是执行下 render
方法就行:
const childVdom = option.render();
render(childVdom);
大家可能平时会写单文件组件 sfc
的形式,那个会有专门的编译器,把 template
编译成 render function
,然后挂到 option 对象的
render` 方法上
所以组件本质上只是对产生 vdom
的逻辑的封装,函数的形式、option
对象的形式、class
的形式都可以。就像 vue3
也有了函数组件一样,组件的形式并不重要。基于 vdom
的前端框架渲染流程都差不多,vue 和 react 很多方面是一样的。但是管理状态的方式不一样,vue
有响应式,而 react
则是 setState
的 api
的方式。真说起来,vue 和 react 最大的区别就是状态管理方式的区别,因为这个区别导致了后面架构演变方向的不同。
状态管理
react
是通过setState
的api
触发状态更新的,更新以后就重新渲染整个vdom
。而vue
是通过对状态做代理,get
的时候收集以来,然后修改状态的时候就可以触发对应组件的render
了。
有的同学可能会问,为什么 react
不直接渲染对应组件呢?
想象一下这个场景:
父组件把它的 setState
函数传递给子组件,子组件调用了它。这时候更新是子组件触发的,但是要渲染的就只有那个组件么?明显不是,还有它的父组件。同理,某个组件更新实际上可能触发任意位置的其他组件更新的。所以必须重新渲染整个 vdom
才行。
那 vue
为啥可以做到精准的更新变化的组件呢?因为响应式的代理呀,不管是子组件、父组件、还是其他位置的组件,只要用到了对应的状态,那就会被作为依赖收集起来,状态变化的时候就可以触发它们的 render
,不管是组件是在哪里的。这就是为什么 react
需要重新渲染整个 vdom
,而 vue
不用。这个问题也导致了后来两者架构上逐渐有了差异。
react 架构的演变
react15
的时候,和vue
的渲染流程还是很像的,都是递归渲染vdom
,增删改dom
就行。但是因为状态管理方式的差异逐渐导致了架构的差异。react
的setState
会渲染整个vdom
,而一个应用的所有vdom
可能是很庞大的,计算量就可能很大。浏览器里js
计算时间太长是会阻塞渲染的,会占用每一帧的动画、重绘重排的时间,这样动画就会卡顿。作为一个有追求的前端框架,动画卡顿肯定是不行的。但是因为setState
的方式只能渲染整个vdom
,所以计算量大是不可避免的。那能不能把计算量拆分一下,每一帧计算一部分,不要阻塞动画的渲染呢?顺着这个思路,react
就改造为了fiber
架构。
fiber 架构
优化的目标是打断计算,分多次进行,但现在递归的渲染是不能打断的,有两个方面的原因导致的:
- 渲染的时候直接就操作了 dom 了,这时候打断了,那已经更新到 dom 的那部分怎么办?
- 现在是直接渲染的 vdom,而 vdom 里只有 children 的信息,如果打断了,怎么找到它的父节点呢?
第一个问题的解决还是容易想到的:
- 渲染的时候不要直接更新到
dom
了,只找到变化的部分,打个增删改的标记,创建好dom
,等全部计算完了一次性更新到dom
就好了。 - 所以
react
把渲染流程分为了两部分:render
和commit
。 render
阶段会找到vdom
中变化的部分,创建dom
,打上增删改的标记,这个叫做reconcile
,调和。reconcile
是可以打断的,由schedule
调度。- 之后全部计算完了,就一次性更新到
dom
,叫做commit
。 - 这样,
react
就把之前的和vue
很像的递归渲染,改造成了render(reconcile + schdule) + commit
两个阶段的渲染。 - 从此以后,
react
和vue
架构上的差异才大了起来。
第二个问题,如何打断以后还能找到父节点、其他兄弟节点呢?
现有的 vdom
是不行的,需要再记录下 parent
、silbing
的信息。所以 react
创造了 fiber
的数据结构。
- 除了
children
信息外,额外多了sibling
、return
,分别记录着兄弟节点、父节点的信息。 - 这个数据结构也叫做
fiber
。(fiber
既是一种数据结构,也代表render + commit
的渲染流程)react
会先把vdom
转换成fiber
,再去进行reconcile
,这样就是可打断的了。 - 为什么这样就可以打断了呢?因为现在不再是递归,而是循环了:
function workLoop() {
while (wip) {
performUnitOfWork();
}
if (!wip && wipRoot) {
commitRoot();
}
}
react
里有一个 workLoop 循环,每次循环做一个fiber
的reconcile
,当前处理的fiber
会放在workInProgress
这个全局变量上。- 当循环完了,也就是
wip
为空了,那就执行commit
阶段,把reconcile
的结果更新到dom
。 - 每个
fiber
的reconcile
是根据类型来做的不同处理。当处理完了当前fiber
节点,就把wip
指向sibling
、return
来切到下个fiber
节点。:
function performUnitOfWork() {
const { tag } = wip;
switch (tag) {
case HostComponent:
updateHostComponent(wip);
break;
case FunctionComponent:
updateFunctionComponent(wip);
break;
case ClassComponent:
updateClassComponent(wip);
break;
case Fragment:
updateFragmentComponent(wip);
break;
case HostText:
updateHostTextComponent(wip);
break;
default:
break;
}
if (wip.child) {
wip = wip.child;
return;
}
let next = wip;
while (next) {
if (next.sibling) {
wip = next.sibling;
return;
}
next = next.return;
}
wip = null;
}
函数组件和class
组件的reconcile
和之前讲的一样,就是调用render
拿到vdom
,然后继续处理渲染出的vdom
:
function updateClassComponent(wip) {
const { type, props } = wip;
const instance = new type(props);
const children = instance.render();
reconcileChildren(wip, children);
}
function updateFunctionComponent(wip) {
renderWithHooks(wip);
const { type, props } = wip;
const children = type(props);
reconcileChildren(wip, children);
}
- 循环执行
reconcile
,那每次处理之前判断一下是不是有更高优先级的任务,就能实现打断了。 - 所以我们在每次处理
fiber
节点的reconcile
之前,都先调用下shouldYield
方法:
function workLoop() {
while (wip && shouldYield()) {
performUnitOfWork();
}
if (!wip && wipRoot) {
commitRoot();
}
}
shouldYiled
方法就是判断待处理的任务队列有没有优先级更高的任务,有的话就先处理那边的fiber
,这边的先暂停一下。- 这就是
fiber
架构的reconcile
可以打断的原理。通过fiber
的数据结构,加上循环处理前每次判断下是否打断来实现的。 - 聊完了
render
阶段(reconcile + schedule
),接下来就进入commit
阶段了。 - 前面说过,为了变为可打断的,
reconcile
阶段并不会真正操作dom
,只会创建dom
然后打个effectTag
的增删改标记。 commit
阶段就根据标记来更新dom
就可以了。- 但是
commit
阶段要再遍历一次fiber
来查找有effectTag
的节点,更新dom
么? - 这样当然没问题,但没必要。完全可以在 reconcile 的时候把有 effectTag 的节点收集到一个队列里,然后 commit 阶段直接遍历这个队列就行了。
- 这个队列叫做
effectList
。 react
会在commit
阶段遍历effectList
,根据effectTag
来增删改dom
。dom
创建前后就是useEffect
、useLayoutEffect
还有一些函数组件的生命周期函数执行的时候。useEffect
被设计成了在dom
操作前异步调用,useLayoutEffect
是在dom
操作后同步调用。- 为什么这样呢?
- 因为都要操作
dom
了,这时候如果来了个effect
同步执行,计算量很大,那不是把 fiber 架构带来的优势有毁了么? - 所以
effect
是异步的,不会阻塞渲染。 - 而
useLayoutEffect
,顾名思义是想在这个阶段拿到一些布局信息的,dom 操作完以后就可以了,而且都渲染完了,自然也就可以同步调用了。 - 实际上
react
把commit
阶段也分成了3
个小阶段。 before mutation
、mutation
、layout
。mutation
就是遍历effectList
来更新dom
的。- 它的之前就是
before mutation
,会异步调度useEffect
的回调函数。 - 它之后就是
layout
阶段了,因为这个阶段已经可以拿到布局信息了,会同步调用useLayoutEffect
的回调函数。而且这个阶段可以拿到新的dom
节点,还会更新下ref
。 - 至此,我们对
react
的新架构,render
、commit
两大阶段都干了什么就理清了。
react-router里的<Link>
标签和<a>
标签有什么区别
对比<a>
,Link
组件避免了不必要的重渲染
使用状态要注意哪些事情?
要注意以下几点。
- 不要直接更新状态
- 状态更新可能是异步的
- 状态更新要合并。
- 数据从上向下流动
这段代码有什么问题?
class App extends Component {
constructor(props) {
super(props);
this.state = {
username: "有课前端网",
msg: " ",
};
}
render() {
return <div> {this.state.msg}</div>;
}
componentDidMount() {
this.setState((oldState, props) => {
return {
msg: oldState.username + " - " + props.intro,
};
});
}
}
render ( < App intro=" 前端技术专业学习平台"></App>,ickt )在页面中正常输出“有课前端网-前端技术专业学习平台”。但是这种写法很少使用,并不是常用的写法。React允许对 setState方法传递一个函数,它接收到先前的状态和属性数据并返回一个需要修改的状态对象,正如我们在上面所做的那样。它不但没有问题,而且如果根据以前的状态( state)以及属性来修改当前状态,推荐使用这种写法。
为什么调用 setState 而不是直接改变 state?
解答
如果您尝试直接改变组件的状态,React 将无法得知它需要重新渲染组件。通过使用setState()
方法,React 可以更新组件的UI。
另外,您还可以谈谈如何不保证状态更新是同步的。如果需要基于另一个状态(或属性)更新组件的状态,请向setState()
传递一个函数,该函数将 state 和 props 作为其两个参数:
this.setState((state, props) => ({
counter: state.counter + props.increment
}));
React的状态提升是什么?使用场景有哪些?
React的状态提升就是用户对子组件操作,子组件不改变自己的状态,通过自己的props把这个操作改变的数据传递给父组件,改变父组件的状态,从而改变受父组件控制的所有子组件的状态,这也是React单项数据流的特性决定的。官方的原话是:共享 state(状态) 是通过将其移动到需要它的组件的最接近的共同祖先组件来实现的。 这被称为“状态提升(Lifting State Up)”。
概括来说就是将多个组件需要共享的状态提升到它们最近的父组件上,在父组件上改变这个状态然后通过props分发给子组件。
一个简单的例子,父组件中有两个input子组件,如果想在第一个输入框输入数据,来改变第二个输入框的值,这就需要用到状态提升。
class Father extends React.Component {
constructor(props) {
super(props)
this.state = {
Value1: '',
Value2: ''
}
}
value1Change(aa) {
this.setState({
Value1: aa
})
}
value2Change(bb) {
this.setState({
Value2: bb
})
}
render() {
return (
<div style={{ padding: "100px" }}>
<Child1 value1={this.state.Value1} onvalue1Change={this.value1Change.bind(this)} />
<Child2 value2={this.state.Value1} />
</div>
)
}
}
class Child1 extends React.Component {
constructor(props) {
super(props)
}
changeValue(e) {
this.props.onvalue1Change(e.target.value)
}
render() {
return (
<input value={this.props.Value1} onChange={this.changeValue.bind(this)} />
)
}
}
class Child2 extends React.Component {
constructor(props) {
super(props)
}
render() {
return (
<input value={this.props.value2} />
)
}
}
ReactDOM.render(
<Father />,
document.getElementById('root')
)
何为高阶组件(higher order component)
高阶组件是一个以组件为参数并返回一个新组件的函数。HOC 运行你重用代码、逻辑和引导抽象。最常见的可能是 Redux 的 connect
函数。除了简单分享工具库和简单的组合,HOC最好的方式是共享 React 组件之间的行为。如果你发现你在不同的地方写了大量代码来做同一件事时,就应该考虑将代码重构为可重用的 HOC。
练习
- 写一个反转其输入的 HOC
- 写一个从 API 提供数据给传入的组件的 HOC
- 写一个实现 shouldComponentUpdate 来避免 reconciliation 的 HOC
- 写一个通过
React.Children.toArray
对传入组件的子组件进行排序的 HOC
转载自:https://segmentfault.com/a/1190000042474495