写点什么

美团前端经典 react 面试题整理

  • 2023-02-28
    浙江
  • 本文字数:5147 字

    阅读完需:约 17 分钟

react 强制刷新

component.forceUpdate() 一个不常用的生命周期方法, 它的作用就是强制刷新


官网解释如下


默认情况下,当组件的 state 或 props 发生变化时,组件将重新渲染。如果 render() 方法依赖于其他数据,则可以调用 forceUpdate() 强制让组件重新渲染。


调用 forceUpdate() 将致使组件调用 render() 方法,此操作会跳过该组件的 shouldComponentUpdate()。但其子组件会触发正常的生命周期方法,包括 shouldComponentUpdate() 方法。如果标记发生变化,React 仍将只更新 DOM。


通常你应该避免使用 forceUpdate(),尽量在 render() 中使用 this.props 和 this.state。


shouldComponentUpdate 在初始化 和 forceUpdate 不会执行

React 父组件如何调用子组件中的方法?

  1. 如果是在方法组件中调用子组件(>= react@16.8),可以使用 useRef 和 useImperativeHandle:


const { forwardRef, useRef, useImperativeHandle } = React;
const Child = forwardRef((props, ref) => { useImperativeHandle(ref, () => ({ getAlert() { alert("getAlert from Child"); } })); return <h1>Hi</h1>;});
const Parent = () => { const childRef = useRef(); return ( <div> <Child ref={childRef} /> <button onClick={() => childRef.current.getAlert()}>Click</button> </div> );};
复制代码


  1. 如果是在类组件中调用子组件(>= react@16.4),可以使用 createRef:


const { Component } = React;
class Parent extends Component { constructor(props) { super(props); this.child = React.createRef(); }
onClick = () => { this.child.current.getAlert(); };
render() { return ( <div> <Child ref={this.child} /> <button onClick={this.onClick}>Click</button> </div> ); }}
class Child extends Component { getAlert() { alert('getAlert from Child'); }
render() { return <h1>Hello</h1>; }}
复制代码

React 中 Diff 算法的原理是什么?

原理如下。(1)节点之间的比较。节点包括两种类型:一种是 React 组件,另一种是 HTML 的 DOM。如果节点类型不同,按以下方式比较。如果 HTML DOM 不同,直接使用新的替换旧的。如果组件类型不同,也直接使用新的替换旧的。如果 HTML DOM 类型相同,按以下方式比较。在 React 里样式并不是一个纯粹的字符串,而是一个对象,这样在样式发生改变时,只需要改变替换变化以后的样式。修改完当前节点之后,递归处理该节点的子节点。如果组件类型相同,按以下方式比较。如果组件类型相同,使用 React 机制处理。一般使用新的 props 替换旧的 props,并在之后调用组件的 componentWillReceiveProps 方法,之前组件的 render 方法会被调用。节点的比较机制开始递归作用于它的子节点。(2)两个列表之间的比较。一个节点列表中的一个节点发生改变, React 无法很妤地处理这个问题。循环新旧两个列表,并找出不同,这是 React 唯一的处理方法。但是,有一个办法可以把这个算法的复杂度降低。那就是在生成一个节点列表时给每个节点上添加一个 key。这个 key 只需要在这一个节点列表中唯一,不需要全局唯一。(3)取舍需要注意的是,上面的启发式算法基于两点假设。类型相近的节点总是生成同样的树,而类型不同的节点也总是生成不同的树可以为多次 render 都表现稳定的节点设置 key。上面的节点之间的比较算法基本上就是基于这两个假设而实现的。要提高 React 应用的效率,需要按照这两点假设来开发。

传入 setState 函数的第二个参数的作用是什么?

该函数会在 setState 函数调用完成并且组件开始重渲染的时候被调用,我们可以用该函数来监听渲染是否完成:


this.setState(  { username: 'tylermcginnis33' },  () => console.log('setState has finished and the component has re-rendered.'))
复制代码


this.setState((prevState, props) => {  return {    streak: prevState.streak + props.count  }})
复制代码

redux 有什么缺点

  • 一个组件所需要的数据,必须由父组件传过来,而不能像flux中直接从store取。

  • 当一个组件相关数据更新时,即使父组件不需要用到这个组件,父组件还是会重新render,可能会有效率影响,或者需要写复杂的shouldComponentUpdate进行判断。

redux 中间件

中间件提供第三方插件的模式,自定义拦截 action -> reducer 的过程。变为 action -> middlewares -> reducer。这种机制可以让我们改变数据流,实现如异步actionaction 过滤,日志输出,异常报告等功能


  • redux-logger:提供日志输出

  • redux-thunk:处理异步操作

  • redux-promise:处理异步操作,actionCreator的返回值是promise


参考 前端进阶面试题详细解答

组件更新有几种方法

  • this.setState() 修改状态的时候 会更新组件

  • this.forceUpdate() 强制更新

  • 组件件 render 之后,子组件使用到父组件中状态,导致子组件的 props 属性发生改变的时候 也会触发子组件的更新

什么是 React 的 refs?为什么它们很重要

refs 允许你直接访问 DOM 元素或组件实例。为了使用它们,可以向组件添加个 ref 属性。如果该属性的值是一个回调函数,它将接受底层的 DOM 元素或组件的已挂载实例作为其第一个参数。可以在组件中存储它。


export class App extends Component {  showResult() {    console.log(this.input.value);  }  render() {    return (      <div>        <input type="text" ref={(input) => (this.input = input)} />        <button onClick={this.showResult.bind(this)}>展示结果</button>      </div>    );  }}
复制代码


如果该属性值是一个字符串, React 将会在组件实例化对象的 refs 属性中,存储一个同名属性,该属性是对这个 DOM 元素的引用。可以通过原生的 DOM API 操作它。


export class App extends Component {  showResult() {    console.log(this.refs.username.value);  }  render() {    return (      <div>        <input type="text" ref="username" />        <button onClick={this.showResu1t.bind(this)}>展示结果</button>      </div>    );  }}
复制代码

React 中 refs 的作用是什么

  • RefsReact 提供给我们的安全访问 DOM元素或者某个组件实例的句柄

  • 可以为元素添加ref属性然后在回调函数中接受该元素在 DOM 树中的句柄,该值会作为回调函数的第一个参数返回

hooks 为什么不能放在条件判断里

以 setState 为例,在 react 内部,每个组件(Fiber)的 hooks 都是以链表的形式存在 memoizeState 属性中



update 阶段,每次调用 setState,链表就会执行 next 向后移动一步。如果将 setState 写在条件判断中,假设条件判断不成立,没有执行里面的 setState 方法,会导致接下来所有的 setState 的取值出现偏移,从而导致异常发生。

useEffect(fn, []) 和 componentDidMount 有什么差异

useEffect 会捕获 props 和 state。所以即便在回调函数里,你拿到的还是初始的 props 和 state。如果想得到“最新”的值,可以使用 ref。

生命周期调用方法的顺序是什么?

React 生命周期分为三大周期,11 个阶段,生命周期方法调用顺序分别如下。(1)在创建期的五大阶段,调用方法的顺序如下。


  • getDetaultProps:定义默认属性数据。

  • getInitialState:初始化默认状态数据。

  • component WillMount:组件即将被构建。

  • render:渲染组件。

  • componentDidMount:组件构建完成


(2)在存在期的五大阶段,调用方法的顺序如下。


  • componentWillReceiveProps:组件即将接收新的属性数据。

  • shouldComponentUpdate:判断组件是否应该更新。

  • componnent WillUpdate:组件即将更新。

  • render:渲染组件。

  • componentDidUpdate:组件更新完成。


(3)在销毁期的一个阶段,调用方法 componentWillUnmount,表示组件即将被销毀。

react 性能优化方案

  • 重写shouldComponentUpdate来避免不必要的 dom 操作

  • 使用 production 版本的react.js

  • 使用key来帮助React识别列表中所有子组件的最小变化

这三个点(...)在 React 干嘛用的?

... 在 React(使用 JSX)代码中做什么?它叫什么?


<Modal {...this.props} title='Modal heading' animation={false}/>
复制代码


这个叫扩展操作符号或者展开操作符,例如,如果this.props包含a:1b:2,则


<Modal {...this.props} title='Modal heading' animation={false}>
复制代码


等价于下面内容:


<Modal a={this.props.a} b={this.props.b} title='Modal heading' animation={false}>
复制代码


扩展符号不仅适用于该用例,而且对于创建具有现有对象的大多数(或全部)属性的新对象非常方便,在更新state 咱们就经常这么做:


this.setState((prevState) => {  return { foo: { ...prevState.foo, a: "updated" } };});
复制代码

diff 算法?


  • 把树形结构按照层级分解,只比较同级元素。

  • 给列表结构的每个单元添加唯一的key属性,方便比较。

  • React 只会匹配相同 classcomponent(这里面的class指的是组件的名字)

  • 合并操作,调用 componentsetState 方法的时候, React 将其标记为 - dirty.到每一个事件循环结束, React 检查所有标记 dirtycomponent重新绘制.

  • 选择性子树渲染。开发人员可以重写shouldComponentUpdate提高diff的性能

在构造函数调用 super 并将 props 作为参数传入的作用是啥?

在调用 super() 方法之前,子类构造函数无法使用this引用,ES6 子类也是如此。将 props 参数传递给 super() 调用的主要原因是在子构造函数中能够通过this.props来获取传入的 props传递 props


class MyComponent extends React.Component {  constructor(props) {    super(props);    console.log(this.props); // { name: 'sudheer',age: 30 }  }}
复制代码


没传递 props


class MyComponent extends React.Component {  constructor(props) {    super();    console.log(this.props); // undefined    // 但是 Props 参数仍然可用    console.log(props); // Prints { name: 'sudheer',age: 30 }  }  render() {    // 构造函数外部不受影响    console.log(this.props); // { name: 'sudheer',age: 30 }  }}
复制代码


上面示例揭示了一点。props 的行为只有在构造函数中是不同的,在构造函数之外也是一样的。

这段代码有什么问题?

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=" 前端技术专业学习平台">,ickt )在页面中正常输出“有课前端网-前端技术专业学习平台”。但是这种写法很少使用,并不是常用的写法。React 允许对 setState 方法传递一个函数,它接收到先前的状态和属性数据并返回一个需要修改的状态对象,正如我们在上面所做的那样。它不但没有问题,而且如果根据以前的状态( state)以及属性来修改当前状态,推荐使用这种写法。

React- Router 有几种形式?

有以下几种形式。HashRouter,通过散列实现,路由要带 #。BrowerRouter,利用 HTML5 中 history API 实现,需要服务器端支持,兼容性不是很好。

如何使用 4.0 版本的 React Router?

React Router 4.0 版本中对 hashHistory 做了迁移,执行包安装命令 npm install react-router-dom 后,按照如下代码进行使用即可。


import { HashRouter, Route, Redirect, Switch } from " react-router-dom";class App extends Component {  render() {    return (      <div>        <Switch>          <Route path="/list" componen t={List}></Route>          <Route path="/detail/:id" component={Detail}>            {" "}          </Route>          <Redirect from="/ " to="/list">            {" "}          </Redirect>        </Switch>      </div>    );  }}const routes = (  <HashRouter>    <App> </App>  </HashRouter>);render(routes, ickt);
复制代码

React 中的 useState() 是什么?

下面说明useState(0)的用途:


const [count, setCounter] = useState(0);const [moreStuff, setMoreStuff] = useState();
const setCount = () => { setCounter(count + 1); setMoreStuff();};
复制代码


useState 是一个内置的 React Hook。useState(0) 返回一个元组,其中第一个参数count是计数器的当前状态,setCounter 提供更新计数器状态的方法。咱们可以在任何地方使用setCounter方法更新计数状态-在这种情况下,咱们在setCount函数内部使用它可以做更多的事情,使用 Hooks,能够使咱们的代码保持更多功能,还可以避免过多使用基于类的组件。


用户头像

还未添加个人签名 2022-09-05 加入

还未添加个人简介

评论

发布
暂无评论
美团前端经典react面试题整理_前端_夏天的味道123_InfoQ写作社区