1.react是什么
react是一个网页UI框架,通过组件化的方式解决视图开发复用的问题,本质是一个组件化框架
- 声明式:更直观,便于组合
- 组件化:试图拆分与模块复用
- 通用性:一次学习,随处编写
2.react生命周期
- 老:
- 初始化阶段(Initialzation)
在constructor中
用来设置属性和state数据,第一个执行的函数 - 挂载阶段(Mounting)
componentWillMount — 组件你将要挂载
render
componentDidMount — 组件完成挂载 - 更新阶段(updation)
componentWillReceiveProps(props) — 组件接受数据
shouldComponentUpdate(nextProps, nextState) — 组件是否更新
componentWillUpdate() — 组件将要更新
render
componentDidUpdate() — 组件完成更新 - 卸载阶段(Unmounting)
componentWillUnmount — 组件将要卸载
- 初始化阶段(Initialzation)
- 新:
- 与旧版生命周期相比,react生命周期即将废弃componentWillMount(),componentWillReceiveProps(),componentWillUpdate()三个钩子,现在使用会出现警告,需要加上UNSAFE_前缀才能使用
- 新版新增了两个钩子:getDerivedStateFromProps()和getSnapshotBeforeUpdate()
- getDerivedStateFromProps():在render()方法之前调用,并且在初始挂载及后续更新时都会被调用。它应返回一个更新state。如果返回null则不更新任何内容;getDerivedStateFromProps(nextProps,prevState)
- getSnapshotBeforeUpdate(prevProps,prevState) — 最后一次渲染输出(提交到DOM节点之前调用,它使得组件在发生更改之前从DOM获取一些信息),此时生命周期的任何返回值将作为参数传递给componentDidUpdate(prevProps,prevState,snashotValue)
- 创建阶段:
constructor
getDerivedStateFromProps
render
componentDidMount - 更新阶段:
getDerivedStateFromProps
shouldComponentUpdate
render
getSnapshotBeforeUpdate
componentDidUpdate - 卸载阶段:
componentWillUnmount
3.useEffect模拟生命周期
// 模拟componentDidMount和componentDidUpdate
useEffect( () => {
})
// 模拟componentDidMount
useEffect( () => {
}, [])
// 模拟componentDidUpdate
useEffect( () => {
}, [name])
// 模拟componentWillUnMount
useEffect( () => {
const id = setInterval(() => {
setCount(c => c + 1)
})
}, [])
4.react性能优化
- 使用shouldComponentUpdate(nextProps, nextState),用于自定义比较逻辑,返回true重新渲染,返回false阻止重新渲染
- 将函数组件变为纯组件,将当前props和上一次的props进行浅层比较,如果相同就阻止组件重复渲染;使用memo方法可以自定义比较逻辑,用于执行深层比较,memo的第二个参数可以接收自定义的比较逻辑(自定义逻辑是函数的形式)
- 使用Fragment避免额外标记:为了满足这个条件我们通常都会在最外层添加一个div,但是这样的话就会多出一个无意义的标记,如果每个组件都多出这样的一个无意义标记的话,浏览器渲染引擎的负担就会加剧。
- 不要使用内联函数定义:在使用内联函数后,render方法每次运行时都会创建该函数的新实例,导致React在进行Virtual DOM比对时,新旧函数比对不相等,导致React总是为元素绑定新的函数实例,而旧的函数实例又要交给垃圾回收期处理。正确的做法是在组件中单独定义函数,将函数绑定给事件
- 避免使用内联样式属性
5.react通信方式
- 父组件向子组件传递
子组件通过props属性就能接受父组件传递过来的参数 - 子组件向父组件传递
父组件向子组件传一个函数,然后通过这个函数的回调,拿到子组件传过来的值 - 父组件向后代组件传递
使用createContext创建一个context,context下存在Provider组件用于创建数据源,Consumer组件用于接收数据 - 非关系型
rendex
6.useMemo和useCallback
useMemo:
当一个父组件中调用了一个子组件的时候,父组件的state发生变化,会导致父组件更新,而子组件虽然没有发生改变,但也会进行更新。
在将useMemo之前,我们先说说memo,memo的作用是结合了pureComponent纯组件和componentShouldUpdate功能,会对传入的props进行一次对比,然后根据第二个函数返回值来进一步判断哪些props需要更新
useMemo(() => (
<div>
{
list.map((item, index) => (
<p key={index}>
{item.name}
</>
)}
}
</div>
),[list])
useMemo的好处:
1.可以减少不必要的循环和不必要的渲染
2.可以减少子组件的渲染次数
3.通过特地的依赖,可以避免很多不必要的开销,但要注意,有时候在配合useState拿不到最新的值,这种情况可以考虑使用useRef解决
useCallback:
useCallback与useMemo及其相似,可以说是一模一样,唯一不同的是useMemo返回的是函数运行的结果,而useCallback返回的是函数
这个函数是父组件传递子组件的一个函数,防止做无关的刷新,其次,这个组件必须配合memo,否则不但不会提高性能,还有可能降低性能。
import React, { useState, useCallback } from 'react';
import { Button } from 'antd-mobile';
const MockMemo: React.FC<any> = () => {
const [count,setCount] = useState(0)
const [show,setShow] = useState(true)
const add = useCallback(()=>{
setCount(count + 1)
},[count])
return (
<div>
<div style={{display: 'flex', justifyContent: 'flex-start'}}>
<TestButton title="普通点击" onClick={() => setCount(count + 1) }/>
<TestButton title="useCallback点击" onClick={add}/>
</div>
<div style={{marginTop: 20}}>count: {count}</div>
<Button onClick={() => {setShow(!show)}}> 切换</Button>
</div>
)
}
const TestButton = React.memo((props:any)=>{
console.log(props.title)
return <Button color='primary' onClick={props.onClick} style={props.title === 'useCallback点击' ? {
marginLeft: 20
} : undefined}>{props.title}</Button>
})
export default MockMemo;
7.Real DOM和Virtual DOM的区别
- 虚拟DOM不会进行排版与重绘操作,而真实DOM会频繁重排和重绘
- 虚拟DOM的总消耗是 “虚拟DOM增删改+真实DOM差异增删改+排版与重绘”,真实DOM的总消耗是“真实DOM完全增删改+排版与重绘”
- Real DOM是真实dom,是真实的文档对象模型,Virtual DOM是虚拟dom,是真实DOM在内存中的一种表现,Real DOM消耗内存较多,更新缓慢,而Virtual DOM很少消耗内存,更新更快
8.state和props的区别
- 相同点:
1. 两者都是JavaScript对象
2. 两者都是用于保存信息
3. props和state都能触发渲染更新 - 区别:
1. props是外部传递给组件的,而state是在组件内被组件自己管理的,一般在constructor中初始化
2. props在组件内部是不可修改的,但state在组件内部可以进行修改
3. state是多变的,可以修改
9.setState的执行机制
state的状态发生改变必须通过setState方法来告知react组件state已经发生了变化。关于setState方法的定义是从React.Component中继承,源码如下:
Component.prototype.setState = function(partialState, callback) {
invariant(
typeof partialState === 'object' ||
typeof partialState === 'function' ||
partialState == null,
'setState(...): takes an object of state variables to update or a ' +
'function which returns an object of state variables.',
);
this.updater.enqueueSetState(this, partialState, callback, 'setState');
};
从上面可以看到setState第一个参数可以是一个对象,或是是一个函数,而第二个参数是一个回调函数,用于可以实时的获取到更新之后的数据
setState本身并不是异步,之所以会有一种异步方法的表现形式,归根结底还是因为react框架本身的性能机制所导致的,因为每次调用setState都会触发更新,异步操作是为了提高性能,将多个状态合并一起更新,减少render调用
- 在组件生命周期或React合成事件中,setState是异步
- 在setTimeout或者原生dom事件中,setState是同步
10.react事件机制
react基于浏览器的事件机制自身实现了一套事件机制,包括事件注册,事件的合成,事件冒泡,事件派发等
-
机制:
1. react所有的事件都挂载到document对象上
2. 当真实dom元素触发事件,会冒泡到document对象后,再处理React事件(好处:不仅仅减少了内存的消耗,还能在组件挂载销毁时统一订阅和移除事件)
3. 所以会先执行原生事件,然后处理react事件
4. 最后真正执行document上挂载的事件 -
react的事件和html事件有什么不同
1. 命名方式:原生事件为全小写,react事件采用小驼峰
2. 事件函数处理语法:原生事件为字符串,react事件为函数
3. 阻止默认行为:react不能通过return false来阻止浏览器的默认行为,而是采用preventDefault -
React并不是将click事件绑定到了div的真实DOM上,而是在document处监听了所有的事件,当事件发生并且冒泡到document处的时候,React将事件内容封装并交由真正的处理函数运行。这样的方式不仅仅减少了内存的消耗,还能在组件挂载销毁时统一订阅和移除事件。
-
除此之外,冒泡到document上的事件也不是原生的浏览器事件,而是由react自己实现的合成事件(SyntheticEvent)。因此如果不想要是事件冒泡的话应该调用event.preventDefault()方法,而不是调用event.stopProppagation()方法。 JSX 上写的事件并没有绑定在对应的真实 DOM 上,而是通过事件代理的方式,将所有的事件都统一绑定在了 document 上。这样的方式不仅减少了内存消耗,还能在组件挂载销毁时统一订阅和移除事件。
-
另外冒泡到 document 上的事件也不是原生浏览器事件,而是 React 自己实现的合成事件(SyntheticEvent)。因此我们如果不想要事件冒泡的话,调用 event.stopPropagation 是无效的,而应该调用 event.preventDefault。
-
实现合成事件的目的如下:
- 合成事件首先抹平了浏览器之间的兼容问题,另外这是一个跨浏览器原生事件包装器,赋予了跨浏览器开发的能力;
- 对于原生浏览器事件来说,浏览器会给监听器创建一个事件对象。如果你有很多的事件监听,那么就需要分配很多的事件对象,造成高额的内存分配问题。但是对于合成事件来说,有一个事件池专门来管理它们的创建和销毁,当事件需要被使用时,就会从池子中复用对象,事件回调结束后,就会销毁事件对象上的属性,从而便于下次复用事件对象。
11.react类组件和函数组件的区别
- 编写方式
- 状态管理
- 生命周期
- 调用方式
- 获取渲染的值
12.受控组件和非受控组件
- 受控组件:简单来说,就是受我们控制的组件,组件的状态全程响应外部数据
- 非受控组件:简单来说,就是不受我们控制的组件,一般情况是在初始化的时候接收外部数据,然后自己在内部存储其自身状态,可以使用ref来从DOM获取值
13.高阶组件
高阶组件即接收一个或多个组件作为参数并且返回一个组件,本质也就是一个函数,至少满足下列一个条件的函数
1.接受一个或多个函数作为输入
2.输出一个函数
class Home extends React.Component {
render() {
return (<h1>Hello World.</h1>);
}
}
function withTiming(WrappedComponent) {
return class extends WrappedComponent {
constructor(props) {
super(props);
this.start = 0;
this.end = 0;
}
componentWillMount() {
super.componentWillMount && super.componentWillMount();
this.start = Date.now();
}
componentDidMount() {
super.componentDidMount && super.componentDidMount();
this.end = Date.now();
console.log(`${WrappedComponent.name} 组件渲染时间为 ${this.end - this.start} ms`);
}
render() {
return super.render();
}
};
}
export default withTiming(Home);
其他适用场景:
1.权限控制
14.react中组件间过渡动画如何实现
在react中实现过渡动画效果会有很多种选择,如react-transition-group,react-motion,Animated,以及原生的CSS都能完成切换动画
15.redux
- redux遵循三大基本原则:
1. 单一数据流
2. state是只读的
3. 使用纯函数来执行修改
通过store.getState()可以获取state
通过store.dispatch(action)来触发state更新
通过store.subscribe(listener)来触发state变化监听器
reducer本质上是根据action.type来更新state并返回nextState的函数
- 首先,用户(通过View)发出Action,发出方式就用到dispatch方法
- 然后,Store自动调用Reducer,并且传入两个参数:当前State和收到的Action,Reducer会返回新的State
- State一旦有变化,Store就会调用监听函数,来更新View
-
如果按照原始的redux工作流程,当组件中产生一个action后会直接触发reducer修改state,reducer又是一个纯函数,也就是不能在reducer中进行异步操作;而往往实际中,组件中发送的action后,在进入reducer之前需要完成一个异步任务,比如发送ajax请求后拿到数据后,再进入reducer,显然原生的redux是不支持这种操作的。
-
redux-saga辅助函数:
- redux-saga提供了一些辅助函数,用来在一些特定的action被发起store时派生任务,来讲一下这两个辅助函数:takeEvery和takeLatest
- 其他effect函数:
- take:take函数可以理解为监听未来的action,它创建了一个命令对象,告诉middleware等待一个特定的action, Generator会暂停,直到一个与pattern匹配的action被发起,才会继续执行下面的语句,也就是说,take是一个阻塞的 effect
function* watchFetchData() {
while(true) {
// 监听一个type为 'FETCH_REQUESTED' 的action的执行,直到等到这个Action被触发,才会接着执行下面的 yield fork(fetchData) 语句
yield take('FETCH_REQUESTED');
yield fork(fetchData);
}
}
-
put:put函数是用来发送action的 effect,你可以简单的把它理解成为redux框架中的dispatch函数,当put一个action后,reducer中就会计算新的state并返回,注意: put 也是阻塞 effect
-
call:call函数你可以把它简单的理解为就是可以调用其他函数的函数,它命令 middleware 来调用fn 函数, args为函数的参数,注意: fn 函数可以是一个 Generator 函数,也可以是一个返回 Promise 的普通函数,call 函数也是阻塞 effect
-
fork:fork 函数和 call 函数很像,都是用来调用其他函数的,但是fork函数是非阻塞函数,也就是说,程序执行完 yield fork(fn, args) 这一行代码后,会立即接着执行下一行代码语句,而不会等待fn函数返回结果后,在执行下面的语句
-
select:select 函数是用来指示 middleware调用提供的选择器获取Store上的state数据,你也可以简单的把它理解为redux框架中获取store上的 state数据一样的功能 :store.getState()
-
redux-sage使用总结:
- 使用 createSagaMiddleware 方法创建 saga 的 Middleware ,然后在创建的 redux 的 store 时,使用 applyMiddleware 函数将创建的 saga Middleware 实例绑定到 store 上,最后可以调用 saga Middleware 的 run 函数来执行某个或者某些 Middleware 。
- 在 saga 的 Middleware 中,可以使用 takeEvery 或者 takeLatest 等 API 来监听某个 action ,当某个 action 触发后, saga 可以使用 call 发起异步操作,操作完成后使用 put 函数触发 action ,同步更新 state ,从而完成整个 State 的更新。
16.react中key的作用
-
元素key属性的作用是用于判断元素是新创建的还是被移动的元素,从而减少不必要的元素渲染
-
keys是React用于追踪哪些列表中元素被修改,被添加或者被移除的辅助标识,在开发过程中,我们需要保证某个元素的key在其他同级元素中具有唯一性
-
在React Diff算法中React会借助元素的key值来判断该元素是新近创建的还是被移动而来的元素,从而减少不必要的元素重渲染
-
此外,React还需要借助Key值来判断元素与本地状态的关联关系,因此我们绝不可忽视转换函数key的重要性
17.react中事件绑定的方式有哪些
1.render方法中使用
render() {
return (
<div onClick={this.handleClick.bind(this)}>test</div>
)
}
2.render方法中使用箭头函数
render() {
return (
<div onClick={ e => this.handleClick(e)}>test</div>
)
}
3.constructor中bind
constructor(props) {
super(props);
this.handleClick = this.handleClick.bind(this)
}
4.定义函数使用箭头函数绑定
handleClick = () => {
console.log( this )
}
18.react和vue的区别
- 数据流向不同:react单向数据流,Vue是双向数据流
- 数据变化:react是不可变数据,Vue是可变数据
- 组件化通信不同:react通过回调函数来进行通信,而Vue子组件向父组件传递信息有两种方式:事件和回调函数
- diff算法不同:react主要使用diff队列保存需要更新哪些DOM,得到path树,再统一操作批量更新DOM,Vue使用双向指针,边对比,边更新DOM
19.虚拟DOM
(1):什么是虚拟DOM
用JS模拟一颗dom树,放在浏览器内存中,当你要变更时,虚拟DOM使用diff算法进行新旧虚拟DOM的比较,将变更放到变更队列中,反应到实际的dom树,减少了dom操作
(2):优点
提高性能:真实DOM的操作,一般都会对某块元素的整体重新渲染。虚拟DOM相当于在dom和js中间加一个缓存,数据变化时,局部变化时,局部刷新变化的地方,减少过多DOM节点排版与重绘损耗
20.hooks的优势
- hooks写法没有this指向问题,代码更加简洁
- hooks通常支持提取和重用跨多个组件通用的有状态逻辑,而无需承担高阶组件或渲染props的负担
- hooks可以轻松地操作函数组件的状态,而不需要将他们转换为类组件
- hooks完全避免了使用生命周期的方法,例如componentDidMount,componentDidUpdate,componentWillUnmounted,相反使用useEffect这样的内置钩子
21.react context
- 可以把context当做是特定一个组件树内共享的store,用来做数据传递。简单说就是,当你不想在组件树中通过逐层传递props或者state的方式来传递数据时,可以使用Context来实现跨层级的组件数据传递。
- 创建 Context:React.createContext() 方法;
- Provider 组件:<Context.Provider value={value}>;
- 消费 value:React.useContext(Context) 方法。
22.react hooks相对于class组件的优势
23.pureComponent
- PureComponent表示一个纯组件,可以用来优化React程序,减少render函数执行的次数,从而提高组件的性能。
- 在React中,当props或者state发生变化时,可以通过在shouldComponentUpdate生命周期函数中执行return false来阻止页面的更新,从而减少不必要的render执行。React.PureComponent会自动执行 shouldComponentUpdate
- 不过,pureComponent中的shouldComponentUpdate()进行的是浅比较,也就是说如果是引用数据类型的数据,只会比较不是同一个地址,而不会比较这个地址里面的数据是否一致。浅比较会忽略属性和状态突变情况,其实也就是数据引用指针没有变化,而数据发生改变的时候render是不会执行的。如果需要重新渲染那么就需要重新开辟空间引用数据。PureComponent一般会用在一些纯展示组件上。
- 使用pureComponent的好处:当组件更新时,如果组件的props或者state都没有改变,render函数就不会触发,省去虚拟DOM的生成和对比过程,达到提升性能的目的,这是因为react自动做了一层浅比较。
24.纯函数组件的好处
- 没有副作用
- 首次render的性能更好
- 语法更简洁,可读性好,逻辑简单,容易复用。
- 不需要进行生命周期的管理和状态管理,所以性能比较好
25.forwardRef
- React.forwardRef 会创建一个React组件,这个组件能够将其接受的ref属性转发到其组件树下的另一个组件中。这个技术并不常见,但在以来两种场景中特别有用:
- 转发refs到DOM组件
- 在高阶组件中转发refs
26.react Fiber
- 分批延时对DOM进行操作,避免一次性操作大量DOM节点,可以得到更好的用户体验
- 给浏览器一点喘息的机会,它会代码进行编译优化及进行热代码优化,或者对reflow进行修正
27.react中可以在render访问refs吗?为什么
不可以,render阶段DOM还没有生成,无法获取DOM
28.有状态组件和无状态组件
- (1)有状态组件
-
特点:
- 是类组件
- 有继承
- 可以使用this
- 可以使用react的生命周期
- 使用较多,容易频繁触发生命周期钩子函数,影响性能
- 内部使用 state,维护自身状态的变化,有状态组件根据外部组件传入的 props 和自身的 state进行渲染。
-
使用场景:
需要使用到状态的。
需要使用状态操作组件的(无状态组件的也可以实现新版本react hooks也可实现) -
总结:
类组件可以维护自身的状态变量,即组件的 state ,类组件还有不同的生命周期方法,可以让开发者能够在组件的不同阶段(挂载、更新、卸载),对组件做更多的控制。类组件则既可以充当无状态组件,也可以充当有状态组件。当一个类组件不需要管理自身状态时,也可称为无状态组件。
-
- (2)无状态组件
-
特点:
- 不依赖自身的状态state
- 可以是类组件或者函数组件。
- 可以完全避免使用 this 关键字。(由于使用的是箭头函数事件无需绑定)
- 有更高的性能。当不需要使用生命周期钩子时,应该首先使用无状态函数组件
- 组件内部不维护 state ,只根据外部组件传入的 props 进行渲染的组件,当 props 改变时,组件重新渲染。
-
使用场景:
- 组件不需要管理 state,纯展示
-
优点:
- 简化代码、专注于 render
- 组件不需要被实例化,无生命周期,提升性能。 输出(渲染)只取决于输入(属性),无副作用
- 视图和数据的解耦分离
-
缺点:
- 无法使用 ref
- 无生命周期方法
- 无法控制组件的重渲染,因为无法使用shouldComponentUpdate 方法,当组件接受到新的属性时则会重渲染
-
总结:
组件内部状态且与外部无关的组件,可以考虑用状态组件,这样状态树就不会过于复杂,易于理解和管理。当一个组件不需要管理自身状态时,也就是无状态组件,应该优先设计为函数组件。比如自定义的 、 等组件。
-
29.react-router的实现原理
- hash路由:通过监听hashchange事件,感知hash变化
改变hash可以直接通过location.hash = xxx - history路由:改变url可以通过history.pushState和resplaceState等,会将URL压入堆栈,同时能够应用history.go()等API
监听url的变化可以通过自定义事件触发实现
30.useEffect和useLayoutEffect的区别
- 使用场景: useEffect 在 React 的渲染过程中是被异步调用的,用于绝大多数场景;而 useLayoutEffect 会在所有的 DOM 变更之后同步调用,主要用于处理 DOM 操作、调整样式、避免页面闪烁等问题。也正因为是同步处理,所以需要避免在 useLayoutEffect 做计算量较大的耗时任务从而造成阻塞。
- 使用效果: useEffect是按照顺序执行代码的,改变屏幕像素之后执行(先渲染,后改变DOM),当改变屏幕内容时可能会产生闪烁;useLayoutEffect是改变屏幕像素之前就执行了(会推迟页面显示的事件,先改变DOM后渲染),不会产生闪烁。useLayoutEffect总是比useEffect先执行。## 31.react数据持久化存储
redux-persist:redux-persist会将redux的store中的数据缓存到浏览器的localStorage中
32.dva
- namespace: 唯一的明明空间
- state: 管理Modal的状态数据
- Action: Action是改变state的唯一途径。通过dispatch函数调用一个action,从而改变对应的数据。
- reducers: 同于 redux 里的 reducer,接收 action,同步更新 state
- Subscription: 用于订阅一个数据源,然后根据条件 dispatch 需要的 action。数据源可以是当前的时间、服务器的 websocket 连接、keyboard 输入、geolocation 变化、history 路由变化等等。
- Effect: 就是异步操作,Effects 的最终流向是通过 Reducers 改变 State。
33. super()和super(props)的区别
- super()就是将父类中的this对象继承给子类的,没有super()子类就得不到this对象
- super(props)在不传递props的情况下,调用this.props为undefined,而传入props的则都能正常访问吗,确保了this.props在构造函数执行完毕之前已被赋值,更符合逻辑
34.React Refs
react中的refs提供了一种方式,允许我们访问DOM节点或在render方法中创建的React元素。本质为ReactDOM.render()返回的组件实例,如果是渲染组件则返回的是组件实例,如果渲染DOM则返回的是具体的DOM节点
- 如何使用:
- 传入字符串,使用时通过this.refs.传入的字符串的格式获取对应的元素
- 传入对象,对象时通过React.createRef()方法创建出来,使用时获取到创建的对象存入current属性就是对应的元素
- 传入函数,该函数会在DOM被挂载时进行回调,这个函数会传入一个元素对象,可以自己保存,使用时,直接拿到之前保存的元素对象即可
- 传入hook,hook是通过useRef()方法创建,使用时通过生成hook对象的current属性就是对应的元素
35.React组件事件代理的原理
- 和原生HTML定义事件的唯一区别就是JSX采用驼峰写法来描述事件名称,大括号中仍然是标准的JavaScript表达式,返回一个事件处理函数。在JSX中不需要关心什么时机去移除事件绑定,因为React会在对应的真实DOM节点移除时就自动解除了事件绑定
- React并不会真正的绑定事件到每一个具体的元素上,而是采用事件代理的模式:在根节点document上为每种事件添加唯一的Listener,然后通过事件的target找到真实的触发元素,这样从触发元素到顶层之间的所有节点如果绑定这个事件,React都会触发响应的事件处理函数,这就是所谓的React模拟事件系统
36.React如何捕获错误
- 错误边界是一种react组件,这种组件可以捕获发生在其子组件树任何位置的JavaScript错误,并打印这些错误,同时展示降级UI,而并不会渲染那些发生崩溃的子组件树
- 错误边界在渲染期间,生命周期方法和整个组件树的构造函数中捕获错误
- 形成错误边界组件的两个条件
- 使用了static getDerivedStateFromError()
- 使用了componentDidCatch
- 抛出错误后,请使用static getDerivedStateFromError()渲染备用UI,使用componentDidCatch()打印错误信息,如下:
class ErrorBoundary extends React.Component {
constructor(props) {
super(props);
this.state = { hasError: false };
}
static getDerivedStateFromError(error) {
// 更新 state 使下一次渲染能够显示降级后的 UI
return { hasError: true };
}
componentDidCatch(error, errorInfo) {
// 你同样可以将错误日志上报给服务器
logErrorToMyService(error, errorInfo);
}
render() {
if (this.state.hasError) {
// 你可以自定义降级后的 UI 并渲染
return <h1>Something went wrong.</h1>;
}
return this.props.children;
}
}
37.高阶组件的缺点
- 组件多层嵌套,增加复杂度,并且ref隔断,需通过React.forwardRef解决
38. hook useState可不可以拿到最新的值
不可以。
可以使用如下办法:
- 在调用useState更新state那个方法中,把最新的值存一下
- 在useEffect中:把需要拿到最新的值写在useEffect第二个参数中,当更新时,触发useEffect回调、
- 通过useRef()
39.useState为什么返回一个数组
如果 useState 返回的是数组,那么使用者可以对数组中的元素命名,代码看起来也比较干净
如果 useState 返回的是对象,在解构对象的时候必须要和 useState 内部实现返回的对象同名,想要使用多次的话,必须得设置别名才能使用返回值
// 第一次使用
const { state, setState } = useState(false);
// 第二次使用
const { state: counter, setState: setCounter } = useState(0)
这里可以看到,返回对象的使用方式还是挺麻烦的,更何况实际项目中会使用的更频繁
40.useEffect在一个函数式组件中放三个依赖什么都一样,他们的执行顺序是什么?
从上到下依次执行
41.在第一个useEffect中用setState,在第二个中能得到吗
拿不到
42.useContext和redux的区别
- useContext解决的是组件之间值传递的问题;redux是应用中统一管理状态的问题
43.useEffect等hooks为什么不能在条件语句中(if…)调用
-
官网指出: 确保总是在你的react函数的最顶层以及任何return之前调用他们。遵守这条规则,你就能确保hook在每一次渲染中都能按照同样的顺序被调用
-
react需要利用调用顺序来正确更新响应的状态,以及调用相应的钩子函数。一旦在循环或条件分支语句中调用Hook,就容易导致调用顺序的不一致性,从而产生难以预料到的后果。
44.为什么react需要合成事件
- 抹平不同浏览器API的差异,更便于跨平台
- 事件合成可以处理兼容性问题
- 利用事件委托机制,支持动态绑定,简化了 DOM 事件处理逻辑,减少了内存开销
45.createElement和cloneElement有什么区别
createElement是JSX被转载得到的,在react中用来创建React元素(即虚拟DOM)的内容。cloneElement用来复制元素并传递新的props
46.为什么建议传递给setState的参数是一个callback而不是一个对象
react为了性能优化,有可能将多个setState调用合并为一次更新。因为this.props和this.state可能时异步更新的,你不能依赖他们的值计算下一个state。
this.setState({
counter: this.state.counter + this.props.increment,
});
接受一个函数。这个函数将接收前一个状态作为第一个参数,应用更新时的 props 作为第二个参数,代码如下:
this.setState((prevState, props) => ({
counter: prevState.counter + props.increment
}));
47.react如何获取组件对应的DOM元素
class式:
createRef
函数式:
useRef
48.react getDefaultProps 它有什么作用
49.Reat.Component和React.PureComponent的区别
PureComponent表示一个纯组件,可以用来优化React程序,减少render函数执行的次数,从而提高组件的性能。
在React中,当prop或者state发生变化,React.PureComponent会自动执行 shouldComponentUpdate。不过,pureComponent中的 shouldComponentUpdate() 进行的是浅比较.
50.Component,Element,Instance之间有什么区别和联系
- 元素: 一个元素element是一个普通对象(plain object),描述了对于一个DOM节点或者其他组件component,你想让它在屏幕上呈现成什么样子。元素element可以在它的属性props中包含其他元素(译注:用于形成元素树)。创建一个React元素element成本很低。元素element创建之后是不可变的。
- 组件: 一个组件component可以通过多种方式声明。可以是带有一个render()方法的类,简单点也可以定义为一个函数。这两种情况下,它都把属性props作为输入,把返回的一棵元素树作为输出。
- 实例: 一个实例instance是你在所写的组件类component class中使用关键字this所指向的东西(译注:组件实例)。它用来存储本地状态和响应生命周期事件很有用。
51.useEffect怎么只在更新时执行?
使用useRef在useEffect做判断
52.Ref时props属性吗
不是
53.在react中遍历的方法有哪些
有React.Children.map和 React.Children.forEach这两个方法,他们的参数都是在组件中接受props.children这个ReactNode作为参数,然后进行遍历。专门提供这两个遍历方法的目的是props.children可能是字符串、null、数组,用React.Children.map可以抹平这些数据类型的差异,使之都能进行循环,并返回合理的值;React.Children.map有返回值(当前组件被遍历的数组,注意React.Fragment不会被遍历), React.Children.forEach没有返回值。
54.为什么列表循环渲染的key最好不要用index
举例说明:
变化前数组的值是[1,2,3,4],key就是对应的下标:0,1,2,3
变化后数组的值是[4,3,2,1],key对应的下标也是:0,1,2,3
- 那么diff算法在变化前的数组找到key =0的值是1,在变化后数组里找到的key=0的值是4
- 因为子元素不一样就重新删除并更新
- 但是如果加了唯一的key,如下
变化前数组的值是[1,2,3,4],key就是对应的下标:id0,id1,id2,id3
变化后数组的值是[4,3,2,1],key对应的下标也是:id3,id2,id1,id0
- 那么diff算法在变化前的数组找到key =id0的值是1,在变化后数组里找到的key=id0的值也是1
- 因为子元素相同,就不删除并更新,只做移动操作,这就提升了性能
55.在react中页面重新加载时怎样保留数据?
- Redux: 将页面的数据存储在redux中,在重新加载页面时,获取Redux中的数据;
- data.js: 使用webpack构建的项目,可以建一个文件,data.js,将数据保存data.js中,跳转页面后获取;
- sessionStorge: 在进入选择地址页面之前,componentWillUnMount的时候,将数据存储到- sessionStorage中,每次进入页面判断sessionStorage中有没有存储的那个值,有,则读取渲染数据;没有,则说明数据是初始化的状态。返回或进入除了选择地址以外的页面,清掉存储的sessionStorage,保证下次进入是初始化的数据
- history API: History API 的 pushState 函数可以给历史记录关联一个任意的可序列化 state,所以可以在路由 push 的时候将当前页面的一些信息存到 state 中,下次返回到这个页面的时候就能从 state 里面取出离开前的数据重新渲染。react-router 直接可以支持。这个方法适合一些需要临时存储的场景。
56.为什么使用jsx的组件中没有看到使用react,却需要引入react
因为react在编辑的时候,写的jsx会被编译成React.createElement,所以这时候如果不引入React,则js会报错 找不到React
57.React.Children.map和js的map有什么区别
JavaScript 中的 map 不会对为 null 或者 undefined 的数据进行处理,而 React.Children.map 中的 map 可以处理 React.Children 为 null 或者 undefined 的情况。
58.什么是prop drlling,如何避免
- 在多层嵌套组件来使用另一个嵌套组件提供的数据。最简单的方法是将一个 prop 从每个组件一层层的传递下去,从源组件传递到深层嵌套组件,这叫做prop drilling。
- prop drilling的主要缺点是原本不需要数据的组件变得不必要地复杂,并且难以维护。
- 为了避免prop drilling,一种常用的方法是使用React Context。通过定义提供数据的Provider组件,并允许嵌套的组件通过Consumer组件或useContext Hook 使用上下文数据。
59.当调用setState时,react render是如何工作的
- 虚拟 DOM 渲染:当render方法被调用时,它返回一个新的组件的虚拟 DOM 结构。当调用setState()时,render会被再次调用,因为默认情况下shouldComponentUpdate总是返回true,所以默认情况下 React 是没有优化的。
- 原生 DOM 渲染:React 只会在虚拟DOM中修改真实DOM节点,而且修改的次数非常少——这是很棒的React特性,它优化了真实DOM的变化,使React变得更快。
60.react是怎么保证多个useState的相互独立的
根据useState出现的顺序来定的
61.React.createClass和extends Component的区别有哪些?
-
(1)语法区别
- createClass本质上是一个工厂函数,extends的方式更加接近最新的ES6规范的class写法。两种方式在语法上的差别主要体现在方法的定义和静态属性的声明上。
- createClass方式的方法定义使用逗号,隔开,因为creatClass本质上是一个函数,传递给它的是一个Object;而class的方式定义方法时务必谨记不要使用逗号隔开,这是ES6 class的语法规范。
-
(2)propType 和 getDefaultProps
- React.createClass:通过proTypes对象和getDefaultProps()方法来设置和获取props.
- React.Component:通过设置两个属性propTypes和defaultProps
-
(3)状态的区别
- React.createClass:通过getInitialState()方法返回一个包含初始值的对象
- React.Component:通过constructor设置初始状态
-
(4)this区别
- React.createClass:会正确绑定this
- React.Component:由于使用了 ES6,这里会有些微不同,属性并不会自动绑定到 React 类的实例上。
-
(5)Mixins
- React.createClass:使用 React.createClass 的话,可以在创建组件时添加一个叫做 mixins 的属性,并将可供混合的类的集合以数组的形式赋给 mixins。
如果使用 ES6 的方式来创建组件,那么 React mixins 的特性将不能被使用了。
- React.createClass:使用 React.createClass 的话,可以在创建组件时添加一个叫做 mixins 的属性,并将可供混合的类的集合以数组的形式赋给 mixins。
62. useEffect(fn, []) 和 componentDidMount 有什么差异?
useEffect 会捕获 props 和 state。所以即便在回调函数里,你拿到的还是初始的 props 和 state。如果想得到“最新”的值,可以使用ref。
63. hooks 为什么不能放在条件判断里?
以 setState 为例,在 react 内部,每个组件(Fiber)的 hooks 都是以链表的形式存在 memoizeState 属性中:update 阶段,每次调用 setState,链表就会执行 next 向后移动一步。如果将 setState 写在条件判断中,假设条件判断不成立,没有执行里面的 setState 方法,会导致接下来所有的 setState 的取值出现偏移,从而导致异常发生。