开启掘金成长之旅!这是我参与「掘金日新计划 · 12 月更文挑战」的第5天,点击查看活动详情
前言
react用于构建前端项目的三大框架之一,使用jsx语法书写代码。由于最近在准备面试的东西,就顺道总结了一部分关于react 相关的面试题,也顺便给自己查缺补漏一下。
问题
- 对于react 的相关了解,以及通常使用react 的时候还会使用那些技术?
- react 的生命周期
- react 组件之间如何传参
- 类组件和函数式组件的区别
- 受控组件和非受控组件的区别
- setState 的更新是同步还是异步?
- 类组件和函数式组件如何绑定ref ?
- React.Component 和 React.PureComponent 的区别
- 常用的Hook 有哪些?
- 自定义Hook 如何编写?
- useEffect 和 useLayoutEffect 的区别
- useMemo 和 useCallback 的区别
- react 中代码复用,有哪些方法?
- 减少react 组件的重复渲染,有哪些方法?
- 如何将某个组件渲染到额外的元素节点上去?以及如何卸载组件?
对于react 的相关了解,以及通常使用react 的时候还会使用那些技术?
- React 是用于构建用户界面的JavaScript 库,起源于Facebook 的内部项目,后将其开源。通常使用jsx 语法进行编写代码,每个 JSX 元素都是调用
React.createElement()的语法糖。通常来说,我们一般在项目中会使用react + react-dom + react-router + react-radux + antd 的结合来开发项目。
- 生命周期详解,生命周期概览
- 通常说到react 生命周期,我们会将其分为两部分,一部分是16.3 以前,一部分是16.3 以后
- 16.3 以前:
- 组件挂载
constructor:在此初始化state,绑定成员函数this,props本地化componentWillMount:组件预挂载,不能进行修改state的操作,在该函数中做的操作,都可以提前到构造函数中去。render:渲染函数,返回的数值用来渲染都页面上去,如果不想页面渲染,可以直接返回null或false。componentDidMount:挂载成功函数。在组件被渲染到DOM树之后调用。
- 组件更新
componentWillReceiveProps(nextProps):该函数在组件进行更新以及父组件render函数被调用后进行执行。通常来比较this.props 和nextProps 来重新setState。shouldComponentUpdate(nextProps,nextState):返回boolean 值,true表示更新,false表示不更新。通常用来避免不必要的组件渲染。componentWillUpdate:预更新函数,当组件收到新的props 和 state,会在渲染之前调用该方法。render:渲染函数。componentDidUpdate:更新完成函数。会在更新后被立即调用。
- 组件卸载
componentWillUnmount:会在组件卸载和销毁之前直接调用。通常会在此函数中执行必要的清理操作,如:清除timer,取消网络请求等。
- 组件挂载
- 16.3 以后:
- 新增了俩个生命周期:
getDerivedStateFromProps,getSnapshotBeforeUpdate - 移除了三个生命周期:
componentWillMount,componentWillReceiveProps,componentWillUpdatestatic getDerivedStateFromProps:会在调用 render 方法之前调用,并且在初始挂载及后续更新时都会被调用,它应返回一个对象来更新 state,如果返回 null 则不更新任何内容。getSnapshotBeforeUpdate:在最近一次渲染输出(提交到 DOM 节点)之前调用。它使得组件能在发生更改之前从 DOM 中捕获一些信息(例如,滚动位置)。此生命周期方法的任何返回值将作为参数传递给 componentDidUpdate()。
- 新增了俩个生命周期:
- 通过
props传递参数
const ChildComp = (props) => {
const {childInfo, changeChildInfo} = props;
return <div>
<div>来自自己的信息</div>
<div>来自父组件的信息:{childInfo}</div>
<div
onClick={() => {
changeChildInfo('改变来自父组件的信息')
}}
>
改变来自父组件的信息
</div>
</div>
}
const ParentComp = () => {
const [childInfo, setChildInfo] = useState('from parent comp')
return <ChildComp childInfo={childInfo} changeChildInfo={setChildInfo} />
}
- 自己定义
发布订阅事件传递参数 - 通过
context传递参数 - 通过
react-redux传递参数
- 类组件创建的时候需要继承
React.Component或者React.PureComponent,能够通过实例化的this,拿到自身的state以及props,而函数式组件内部没有this,需要结合一些Hook方法来保存组件的state,以及替代生命周期函数。 - 当我们需要在循环子组件,进行判断是类组件和函数式组件的时候,可以通过判断子组件原型上是否拥有
isReactComponent属性。
- 受控组件:使用
state来控制组件的数据源,如果需要改变组件的数据源的话,就需要通过setState来进行改变。 - 非受控组件:组件数据源没有绑定
state,而是通过ref来对组件进行绑定,获取对应的DOM元素。
setState数据进行更新的时候,有可能是同步,也有可能是异步,具体需要看setState是在什么时机进行执行。- 如果
setState在一些原生事件中会同步更新,如:addEventListener,setTimeout,setInterval等。 - 而当
setState处于React自身的事件机制中的时候,就会进行异步更新。
- 类组件绑定ref,先通过
React.createRef()创建ref,然后直接将ref 当作属性传递给对应的组件。
class ChildComp extends React.Component {
render() {
const { ref } = this.props;
return <div ref={ref}>
子组件展示内容
</div>
}
}
class ParentComp extends React.Component {
ref = React.createRef()
render() {
<ChildComp ref={this.ref} />
}
}
- 函数式组件绑定ref,需要想利用
React.forwardRef将组件包裹,然后在第二个参数上拿到ref,来进行绑定,
const ChildComp = (props, ref) => {
return <div ref={ref}>子组件展示内容</div>
}
const ChildCompForward = React.forwardRef(ChildComp);
const ParentComp = () => {
const childRef = useRef();
return <ChildCompForward ref={childRef} />
}
- 需要注意的是,还可以通过
useImperativeHandle方法来对子组件的state和方法 来做暴露。
const ChildComp = (props, ref) => {
useImperativeHandle(ref, () => ({
log: () => {
console.log('子组件暴露的log 方法')
}
}))
return <div>子组件展示内容</div>
}
const ChildCompForward = React.forwardRef(ChildComp);
const ParentComp = () => {
const childRef = useRef();
return <ChildCompForward ref={childRef} />
}
React.Component 和 React.PureComponent 的区别
- 两者的区别在于
React.Component并未实现shouldComponentUpdate,而React.PureComponent中以浅层对比prop和state的方法来实现了该函数。需要注意的是,PureComponent仅做对象的浅层比较,如果对象包含了复杂的数据结构,会产生错误的比对结果。
useState, useEffect, useMemo, useCallback, useRef, useContext等。
- 自定义Hook 是一个函数,其名称以‘use’ 开头,函数内部可以调用其他Hook。
/**
* 自定hook,通过该hook 传递点击事件event,来计算出对应元素的 left,top
* 方便将这些信息传递给需要进行定位显示的元素,如:modal弹窗,pop气泡等组件
*/
const usePos = (event) => {
const [pos, setPos] = useState();
const changePos = (e) => {
const { left, top } = e.target.getBoundingClientRect();
setPos({
left,
top
});
}
changePos(e);
return [pos, changePos];
}
useEffect 和 useLayoutEffect 的区别
useEffect主要是可以帮助函数式组件模拟一定的生命周期函数,以及在这个函数中,来处理一定的副作用操作,比如发起请求,进行定时器等操作。useLayoutEffect跟useEffect的作用差不多,区别只是在与执行时机的不同,useLayoutEffect会在所有的DOM变更之后同步调用effect,可以使用它来读取DOM 布局并同步触发渲染,在浏览器执行绘制之前,useLayoutEffect内部的更新计划将被同步刷新。
useMemo会返回一个惰性值,如果这个返回值的相关依赖没有进行更新的话,这个惰性值也不会进行更新。useCallback会返回一个惰性函数,如果相关依赖没有进行更新,那么该函数也不会进行更新。
/**
* 在下面代码中,value,callback 这两个值在初始化后,不会再进行更新
*/
const value = useMemo(() => {
return 1;
}, []);
const callback = useCallback(() => {
console.log('调用了callback 函数');
}, []);
- 通常来说,进行代码复用或者说是组件复用的话,会有以下三种方法:
hoc高阶函数:通过高阶组件,对相应组件进行包裹处理,传递一部分相同的属性给对应组件。render Props:将对应组件通过props 属性传给组件,然后拿到组件内部的state 等参数。hooks:通过编写自定义hook 来实现一段代码或者逻辑的复用。
- 一般来说,对react 性能的优化,其实就是减少组件的不必要渲染次数,可以通过以下方法来减少次数:
React.memo包裹组件,传递自定义方法,进行比对。useMemo,useCallback等Hook- 通过
PureComponent,shouldComponentUpdate等方法来减少类组件的渲染次数。
- react 自己提供了相关的方法:
ReactDom.createPortal(child, container): 将对应的child 组件渲染到container 元素上去。unmountComponentAtNode:从DOM 中卸载组件。
参考文档: