开课吧 React 面试题¶
统计信息:字数 9916 阅读20分钟
1、key 是什么?有什么作用?¶
key 用于标记同级元素的唯一标示。作用是标识唯一性(渲染的时候是否重复渲染节点,如果是同一个元素,直接复用这个元素);Fiber原来是一个链表结构,通过 key 的唯一性特性转换成一个 Map对象,方便读取Fiber节点(React源码)。
2、ref 是什么?创建的三种方式?怎样使用?¶
Ref目的是获取原生的DOM节点,进一步获取元素的尺寸,或者表单的值。
具体可以给某个HTML元素设置REF,直接获取DOM节点。或者给某个类组件设置REF,直接获取组件对象(然后获取内部的属性和方法)。
创建REF¶
推荐 this.refs = React.createRef()
创建 REF,然后通过 current 获取DOM节点。
直接使用(例子)input
this.nameInputRef = React.createRef();
let value = this.nameInputRef.current.value;
// <input type="text" ref={this.nameInputRef}/>
回调函数获取REF
setRef = node => {
this.ref = node;
}
// <input type="text" ref={this.setRef}>
内联函数在每次render都会创建一个匿名函数,重新设置REF,尽量减少使用。
// <input type="text" ref={node => {this.ref = node;}>
不推荐使用字符串 this.refs.textInput 创建 REF,大量使用有性能问题。
实际项目可能三种方法都使用(有旧代码)所以三个都要会用。
转发REF¶
类组件可以直接使用REF,函数组件不能直接使用,需要通过转换REF转发设置REF。
// 函数组件需要通过REF转发后获取
const NewComponent = React.formardRef(FunctionComponent);
// render <NewComponent ...props ref={this.newRef}>
function FunctionComponent(props, ref) {
return (
<input ref={ref}/ type='text'>
);
}
在HOC高阶函数中转发REF:包裹组件不能直接使用子组件的REF,所以需要使用 forwardRef 来转发一下,把内部的REF转发到包裹层外部的组件。使用高阶函数时,设置一个REF,this.inputRef.current 获取子组件的 ref。
const hoc = WrapComponent => {
React.forwardRef((props, ref) => {
return (
<div>
<WrapComponent {...props} innerRef={ref}/>
</div>
);
});
}
在 HOOK 中的使用
function Input(props) {
const inpurRef = useRef(null);
// inputRef.current.value 使用
return (
<input type="text" ref={inputRef}/>
);
}
总结:三种创建方式、两种转发REF的情况、一个在HOOK中使用情况(useRef)(记忆 321)
3、生命周期函数¶
生命周期函数阶段¶
组件挂载、更新、卸载阶段。
挂载阶段:constructor 构造器、componentWillMount(过时了,不使用)、componentDidMount(发出请求,绑定事件,获取DOM节点尺寸)
更新阶段:shouldComponentUpdate、componentWillUpdate、componentDidUpdate、getDrivedStateFromProps、componentWillReceiveProps(过时了,不使用) 避免在其中setState
卸载阶段:componentWillUnmount(解绑事件,删除定时器等)
新增和删除的生命周期¶
1、componentWillMount 在服务端处理请求,大规模并发请求不利于服务器,减少使用;
2、componentWillReceiveProps 避免使用,改成 static getDerivedStateFromProps(nextProps, prevState) 对比前后的不同,然后更新state(同时增加一个state)
static getDerivedStateFromProps(props, state) {
const { count } = this.state;
return count > 5 { count: 0 } : null;
}
// 这是一个静态函数,用于替代componentWillReceiveProps
// 具体的实现原理(fiber内部)如果传入了这个函数,就从Props中生成state状态
// 然后将状态存储在实例中
const getDerivedStateFromProps = ctor.getDerivedStateFromProps;
if (typeof getDerivedStateFromProps === 'function') {
applyDerivedStateFromProps(
workInProgress,
ctor,
getDerivedStateFromProps,
newProps,
);
instance.state = workInProgress.memoizedState;
}
// 使用TS进行参数类型验证
function applyDerivedStateFromProps(
workInProgress: Fiber,
ctor: any,
getDerivedStateFromProps: (props: any, state: any) => any,
nextProps: any
) {
const prevState = workInProgress.memoizedState;
const memoizedState = partialState === null || partialState === undifined ? prevState : Object.assign({}, prevState, partialState);
// 如果更新后的状态不是空,那么使用新的state合并原始的state
workInProgress.memoizedState = memoizedState;
}
3、componentWillUpdate 这个在组件更新前执行,避免使用。使用 getSnapshotBeforeUpdate 代替。
内部判断:如果有 getDirevedStateFromProps 或者 getSnapshotBeforeUpdate 就有新的生命周期函数。这两个函数不能和即将废弃的旧函数同时使用。
const snapshot = instance.getSnapshotBeforeUpdate(
finishedWork.elementType === finishedWork.type
? prevProps
: resolveDefaultProps(finishedWork.type, prevProps),
prevState
);
instance.__reactInternalSnapshotBeforeUpdate = snapshot;
4、其他
组件什么时候会更新?Props 变化、state 变化(setState)forceUpdate 触发
组件卸载?只执行一次 componentWillUnmount
const instance = current.stateNode;
if (typeof instance.componentWillUnmount === 'function') {
safelyCallComponentWillUnmount(current, instance);
}
4、事件系统¶
这里主要讲合成事件和原生事件的关系;合成事件对应的一个表。合成事件和异步操作的执行过程。
// reactDOMComponent
function setInitialDOMProperties (
tag: string,
domElement: Element,
rootContainerElement: Element | Document,
nextProps: Object,
isCustomComponentTag: boolean,
): void {
for (const propKey in nextProps) {
// 排除了原型链上的属性
if (!nextProps.hasOwnProperty(propKey)) {
continue;
}
const nextProp = nextProps[propKey];
// nextProp 根据这个的数据类型,然后分别执行不同的操作
}
}
5、setState描述¶
setState 可以传两个参数:第一个是 Object 或者 function,然后获取一个新的状态,然后和原来的状态合并后更新界面。第二个参数是更新界面后的回调函数。
setState 通常是异步执行的。如果在 setTimeout内部,那么就是同步执行的。
具体执行过程
Component.prototype.setState = function(partialState, callback) {
invariant(
typeof partialState === 'object' ||
typeof partialState === 'function' ||
partialState == null,
'setState(...): 描述文字',
);
// 进入队列
this.updater.enqueueSetState(this, partialState, callback, 'setState');
};
// 具体执行
enqueueUpdate(fiber, update);
scheduleUpdateOnFiver(fiber, expirationTime);
6、react组件通信(redux)¶
跨层级通信:context 跨越多个层级通信(redux)
React-Router location 可以使用 context 上下文传参
- Context 简介
React中,使用 context 实现组件跨层级传值
React.createContext();
React.Provider
React.contextType
React.Consumer
基本使用
import React, {Component,useContext} from 'react';
const MyContext = React.createContext({});
class ClassChild extends Component {
static contextType = MyContext;
render() {
const {color} = this.context;
return (
<button>
style={{color: color}}
</button>
);
}
}
Context 可能会重复渲染:当Provider父组件进行重重复渲染时,可能在consumerszizujian 中重复渲染-传参如果是对象,那么每次对象更新都会重复渲染子组件。
解决方法:不传递对象,传递一个字符串等;或者把状态提升到父节点的state中
this.state = {
value: {key: 'test'}
};
render() {
return (
<Provider value={this.state.value}>
<Toolbar/>
</Provider>
);
}
7、函数组件和类组件如何选择¶
没有HOOK的情况:函数组件主要展示功能,不支持复杂数据交互;类组件中支持state设置和其他数据交互等(无状态、无副作用)。
类组件缺点:组件之间复用状态很难;复杂组件维护较难;this指向问题。如果一个组件有上面的缺点,可以使用函数组件
有HOOK的情况:函数组件可以存储改变状态(useState useReducer)可以执行副作用复用逻辑(useEffect, useLayoutEffect, 自定义Hook等)
// 自定义hook-必须以use开头
// 在函数中,不需要写render,直接使用State保存状态
function useClock() {
const [date, setDate] = useState(new Date());
useEffect(() => {
// didMount 阶段执行
const timer = setInterval(() => {
setDate(newDate());
}, 1000);
// UNmount阶段,清除定时器
return () => clearInterval(timer);
}, []);
return date;
}
8、react性能优化¶
整理思路:减少JS运算,减少界面渲染,减少HTTP请求
- 减少JS运算:优化算法,减少时间复杂度
- 减少不必要的渲染:shouldComponentUpdate、PureComponent、React.memo(函数组件中实现PureComponent的功能)
- 数据缓存:原生JS中使用cache缓存数据;HOOK中使用 useMemo useCallback 缓存函数
function updateMemo<T>(
nextCreate: () => T,
deps: Array<mixed> | void | null,
): T {
const hook = updateWorkInProgressHook();
const nextDeps = deps === undefined ? null : deps;
const prevState = hook.memorizedState;
//...
const nextValue = nextCtrete();
hook.memoizedState = [nextValue, nextDeps];
return nextValue;
}
-
函数对象尽量不要使用内联形式:每次执行,会创建一个新的对象,子组件中对比不同,会重新渲染子组件。
-
不要滥用 props context(redux传参)
- 长页面大图片懒加载
- 减少请求等