关于react.js:前端面试指南之React篇二

38次阅读

共计 11278 个字符,预计需要花费 29 分钟才能阅读完成。

react 中这两个生命周期会触发死循环

componentWillUpdate生命周期在 shouldComponentUpdate 返回 true 后被触发。在这两个生命周期只有视图更新就会触发,因而不能再这两个生命周期中应用 setState。否则会导致死循环

react 性能优化是在哪个生命周期函数中

在 shouldComponentUpdate 这个办法中,这个办法次要用来判断是否须要调用 render 办法重绘 DOM
因为 DOM 的描述十分耗费性能,如果可能在 shouldComponentUpdate 办法中能写出更优化的 diff 算法,极大的进步性能

React 有哪些优化性能的伎俩

类组件中的优化伎俩

  • 应用纯组件 PureComponent 作为基类。
  • 应用 React.memo 高阶函数包装组件。
  • 应用 shouldComponentUpdate 生命周期函数来自定义渲染逻辑。

办法组件中的优化伎俩

  • 应用 useMemo
  • 应用 useCallBack

其余形式

  • 在列表须要频繁变动时,应用惟一 id 作为 key,而不是数组下标。
  • 必要时通过扭转 CSS 款式暗藏显示组件,而不是通过条件判断显示暗藏组件。
  • 应用 Suspense 和 lazy 进行懒加载,例如:
import React, {lazy, Suspense} from "react";

export default class CallingLazyComponents extends React.Component {render() {
    var ComponentToLazyLoad = null;

    if (this.props.name == "Mayank") {ComponentToLazyLoad = lazy(() => import("./mayankComponent"));
    } else if (this.props.name == "Anshul") {ComponentToLazyLoad = lazy(() => import("./anshulComponent"));
    }

    return (
      <div>
        <h1>This is the Base User: {this.state.name}</h1>
        <Suspense fallback={<div>Loading...</div>}>
          <ComponentToLazyLoad />
        </Suspense>
      </div>
    )
  }
}

React 的生命周期办法有哪些?

  • componentWillMount: 在渲染之前执行,用于根组件中的 App 级配置。
  • componentDidMount:在第一次渲染之后执行,能够在这里做 AJAX 申请,DOM 的操作或状态更新以及设置事件监听器。
  • componentWillReceiveProps:在初始化 render 的时候不会执行,它会在组件承受到新的状态 (Props) 时被触发,个别用于父组件状态更新时子组件的从新渲染
  • shouldComponentUpdate:确定是否更新组件。默认状况下,它返回true。如果确定在 stateprops 更新后组件不须要在从新渲染,则能够返回false,这是一个进步性能的办法。
  • componentWillUpdate:在 shouldComponentUpdate 返回 true 确定要更新组件之前件之前执行。
  • componentDidUpdate:它次要用于更新 DOM 以响应 propsstate更改。
  • componentWillUnmount:它用于勾销任何的网络申请,或删除与组件关联的所有事件监听器。

约束性组件(controlled component)与非约束性组件(uncontrolled component)有什么区别?

在 React 中,组件负责管制和治理本人的状态。
如果将 HTML 中的表单元素(input、select、textarea 等)增加到组件中,当用户与表单产生交互时,就波及表单数据存储问题。依据表单数据的存储地位,将组件分成约東性组件和非约東性组件。
约束性组件(controlled component)就是由 React 管制的组件,也就是说,表单元素的数据存储在组件外部的状态中,表单到底出现什么由组件决定。
如下所示,username 没有存储在 DOM 元素内,而是存储在组件的状态中。每次要更新 username 时,就要调用 setState 更新状态;每次要获取 username 的值,就要获取组件状态值。

class App extends Component {
  // 初始化状态
  constructor(props) {super(props);
    this.state = {username: "有课前端网",};
  }
  // 查看后果
  showResult() {
    // 获取数据就是获取状态值
    console.log(this.state.username);
  }
  changeUsername(e) {
    // 原生办法获取
    var value = e.target.value;
    // 更新前,能够进行脏值检测
    // 更新状态
    this.setState({username: value,});
  }
  // 渲染组件
  render() {
    // 返回虚构 DOM
    return (
      <div>
        <p>
          {/* 输入框绑定 va1ue*/}
          <input type="text" onChange={this.changeUsername.bind(this)} value={this.state.username} />
        </p>
        <p>
          <button onClick={this.showResult.bind(this)}> 查看后果 </button>
        </p>
      </div>
    );
  }
}

非约束性组件(uncontrolled component)就是指表单元素的数据交由元素本身存储并解决,而不是通过 React 组件。表单如何出现由表单元素本身决定。
如下所示,表单的值并没有存储在组件的状态中,而是存储在表单元素中,当要批改表单数据时,间接输出表单即可。有时也能够获取元素,再手动批改它的值。当要获取表单数据时,要首先获取表单元素,而后通过表单元素获取元素的值。
留神:为了不便在组件中获取表单元素,通常为元素设置 ref 属性,在组件外部通过 refs 属性获取对应的 DOM 元素。

class App extends Component {
  // 查看后果
  showResult() {
    // 获取值
    console.log(this.refs.username.value);
    // 批改值,就是批改元素本身的值
    this.refs.username.value = "业余前端学习平台";
    // 渲染组件
    // 返回虚构 DOM
    return (
      <div>
        <p>
          {/* 非约束性组件中,表单元素通过 defaultvalue 定义 */}
          <input type="text" ref="username" defaultvalue="有课前端网" />
        </p>
        <p>
          <button onClick={this.showResult.bind(this)}> 查看后果 </button>
        </p>
      </div>
    );
  }
}

尽管非约東性组件通常更容易实现,能够通过 refs 间接获取 DOM 元素,并获取其值,然而 React 倡议应用约束性组件。次要起因是,约東性组件反对即时字段验证,容许有条件地禁用 / 启用按钮,强制输出格局等。

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

  1. 如果是在办法组件中调用子组件(>= [email protected]),能够应用 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. 如果是在类组件中调用子组件(>= [email protected]),能够应用 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>;}
}

传入 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}
})

参考:前端 react 面试题具体解答

类组件与函数组件有什么异同?

相同点: 组件是 React 可复用的最小代码片段,它们会返回要在页面中渲染的 React 元素。也正因为组件是 React 的最小编码单位,所以无论是函数组件还是类组件,在应用形式和最终出现成果上都是完全一致的。

咱们甚至能够将一个类组件改写成函数组件,或者把函数组件改写成一个类组件(尽管并不举荐这种重构行为)。从使用者的角度而言,很难从应用体验上辨别两者,而且在古代浏览器中,闭包和类的性能只在极其场景下才会有显著的差异。所以,根本可认为两者作为组件是完全一致的。

不同点:

  • 它们在开发时的心智模型上却存在微小的差别。类组件是基于面向对象编程的,它主打的是继承、生命周期等外围概念;而函数组件内核是函数式编程,主打的是 immutable、没有副作用、援用通明等特点。
  • 之前,在应用场景上,如果存在须要应用生命周期的组件,那么主推类组件;设计模式上,如果须要应用继承,那么主推类组件。但当初因为 React Hooks 的推出,生命周期概念的淡出,函数组件能够齐全取代类组件。其次继承并不是组件最佳的设计模式,官网更推崇“组合优于继承”的设计概念,所以类组件在这方面的劣势也在淡出。
  • 性能优化上,类组件次要依附 shouldComponentUpdate 阻断渲染来晋升性能,而函数组件依附 React.memo 缓存渲染后果来晋升性能。
  • 从上手水平而言,类组件更容易上手,从将来趋势上看,因为 React Hooks 的推出,函数组件成了社区将来主推的计划。
  • 类组件在将来工夫切片与并发模式中,因为生命周期带来的复杂度,并不易于优化。而函数组件自身轻量简略,且在 Hooks 的根底上提供了比原先更细粒度的逻辑组织与复用,更能适应 React 的将来倒退。

对于 store 的了解

Store 就是把它们分割到一起的对象。Store 有以下职责:

  • 维持利用的 state;
  • 提供 getState() 办法获取 state;
  • 提供 dispatch(action) 办法更新 state;
  • 通过 subscribe(listener)注册监听器;
  • 通过 subscribe(listener)返回的函数登记监听器

应用状态要留神哪些事件?

要留神以下几点。

  • 不要间接更新状态
  • 状态更新可能是异步的
  • 状态更新要合并。
  • 数据从上向下流动

如何用 React 构建(build)生产模式?

通常,应用 Webpack 的 DefinePlugin 办法将 NODE ENV 设置为 production。这将剥离 propType 验证和额定的正告。除此之外,还能够缩小代码,因为 React 应用 Uglify 的 dead-code 来打消开发代码和正文,这将大大减少包占用的空间。

react 组件的划分业务组件技术组件?

  • 依据组件的职责通常把组件分为 UI 组件和容器组件。
  • UI 组件负责 UI 的出现,容器组件负责管理数据和逻辑。
  • 两者通过 React-Redux 提供connect 办法分割起来

在生命周期中的哪一步你应该发动 AJAX 申请

咱们该当将 AJAX 申请放到 componentDidMount 函数中执行,次要起因有下

  • React 下一代和谐算法 Fiber 会通过开始或进行渲染的形式优化利用性能,其会影响到 componentWillMount 的触发次数。对于 componentWillMount 这个生命周期函数的调用次数会变得不确定,React 可能会屡次频繁调用 componentWillMount。如果咱们将 AJAX 申请放到 componentWillMount 函数中,那么不言而喻其会被触发屡次,天然也就不是好的抉择。
  • 如果咱们将 AJAX 申请搁置在生命周期的其余函数中,咱们并不能保障申请仅在组件挂载结束后才会要求响应。如果咱们的数据申请在组件挂载之前就实现,并且调用了setState 函数将数据增加到组件状态中,对于未挂载的组件则会报错。而在 componentDidMount 函数中进行 AJAX 申请则能无效防止这个问题

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

key 能够帮忙 React 跟踪循环创立列表中的虚构 DOM 元素,理解哪些元素已更改、增加或删除。
每个绑定 key 的虚构 DOM 元素,在兄弟元素之间都是举世无双的。在 React 的和解过程中,比拟新的虛拟 DOM 树与上一个虛拟 DOM 树之间的差别,并映射到页面中。key 使 React 解决列表中虛拟 DOM 时更加高效,因为 React 能够应用虛拟 DOM 上的 key 属性,疾速理解元素是新的、须要删除的,还是批改过的。如果没有 key,Rεat 就不晓得列表中虚构 DOM 元素与页面中的哪个元素绝对应。所以在创立列表的时候,不要疏忽 key。

hooks 和 class 比拟的劣势?

一、更容易复用代码

二、清新的代码格调 + 代码量更少

毛病

状态不同步
不好用的 useEffect,

为什么要应用 React. Children. map(props. children,()=>)而不是 props. children. map (() => )?

因为不能保障 props. children 将是一个数组。
以上面的代码为例。

<Parent>
    <h1> 有课前端网 </h1>
</Parent>

在父组件外部,如果尝试应用 props.children. map 映射子对象,则会抛出谬误,因为 props. children 是一个对象,而不是一个数组。
如果有多个子元素,React 会使 props.children 成为一个数组,如下所示。

<Parent>
  <h1> 有课前端网 </h1>
  <h2> 前端技术学习平台 </h2>
</Parent>;
// 不倡议应用如下形式,在这个案例中会抛出谬误。class Parent extends Component {render() {return <div> {this.props.children.map((obj) => obj)}</div>;
  }
}

倡议应用如下形式,防止在上一个案例中抛出谬误。

class Parent extends Component {render() {return <div> {React.Children.map(this.props.children, (obj) => obj)}</div>;
  }
}

useEffect 与 useLayoutEffect 的区别

(1)共同点

  • 使用成果: useEffect 与 useLayoutEffect 两者都是用于解决副作用,这些副作用包含扭转 DOM、设置订阅、操作定时器等。在函数组件外部操作副作用是不被容许的,所以须要应用这两个函数去解决。
  • 应用形式: useEffect 与 useLayoutEffect 两者底层的函数签名是完全一致的,都是调用的 mountEffectImpl 办法,在应用上也没什么差别,根本能够间接替换。

(2)不同点

  • 应用场景: useEffect 在 React 的渲染过程中是被异步调用的,用于绝大多数场景;而 useLayoutEffect 会在所有的 DOM 变更之后同步调用,次要用于解决 DOM 操作、调整款式、防止页面闪动等问题。也正因为是同步解决,所以须要防止在 useLayoutEffect 做计算量较大的耗时工作从而造成阻塞。
  • 应用成果: useEffect 是依照程序执行代码的,扭转屏幕像素之后执行(先渲染,后扭转 DOM),当扭转屏幕内容时可能会产生闪动;useLayoutEffect 是扭转屏幕像素之前就执行了(会推延页面显示的事件,先扭转 DOM 后渲染),不会产生闪动。useLayoutEffect 总是比 useEffect 先执行。

在将来的趋势上,两个 API 是会长期共存的,临时没有删减合并的打算,须要开发者依据场景去自行抉择。React 团队的倡议十分实用,如果切实分不清,先用 useEffect,个别问题不大;如果页面有异样,再间接替换为 useLayoutEffect 即可。

**

React 与 Vue 的 diff 算法有何不同?

diff 算法是指生成更新补丁的形式,次要利用于虚构 DOM 树变动后,更新实在 DOM。所以 diff 算法肯定存在这样一个过程:触发更新 → 生成补丁 → 利用补丁。

React 的 diff 算法,触发更新的机会次要在 state 变动与 hooks 调用之后。此时触发虚构 DOM 树变更遍历,采纳了深度优先遍历算法。但传统的遍历形式,效率较低。为了优化效率,应用了分治的形式。将繁多节点比对转化为了 3 种类型节点的比对,别离是树、组件及元素,以此晋升效率。

  • 树比对:因为网页视图中较少有跨层级节点挪动,两株虚构 DOM 树只对同一档次的节点进行比拟。
  • 组件比对:如果组件是同一类型,则进行树比对,如果不是,则间接放入到补丁中。
  • 元素比对:次要产生在同层级中,通过标记节点操作生成补丁,节点操作对应实在的 DOM 剪裁操作。

以上是经典的 React diff 算法内容。自 React 16 起,引入了 Fiber 架构。为了使整个更新过程可随时暂停复原,节点与树别离采纳了 FiberNode 与 FiberTree 进行重构。fiberNode 应用了双链表的构造,能够间接找到兄弟节点与子节点。整个更新过程由 current 与 workInProgress 两株树双缓冲实现。workInProgress 更新实现后,再通过批改 current 相干指针指向新节点。

Vue 的整体 diff 策略与 React 对齐,尽管不足工夫切片能力,但这并不意味着 Vue 的性能更差,因为在 Vue 3 初期引入过,前期因为收益不高移除掉了。除了高帧率动画,在 Vue 中其余的场景简直都能够应用防抖和节流去进步响应性能。

为什么有些 react 生命周期钩子被标记为 UNSAFE

componentWillMount

componentWillMount 生命周期产生在首次渲染前,个别应用的小伙伴大多在这里初始化数据或异步获取内部数据赋值。初始化数据,react 官网倡议放在 constructor 外面。而异步获取内部数据,渲染并不会期待数据返回后再去渲染

class Example extends React.Component {   
    state = {value: ''};
    componentWillMount() {    
        this.setState({value: this.props.source.value});       
        this.props.source.subscribe(this.handleChange);
    }   
    componentWillUnmount() {this.props.source.unsubscribe(this.handleChange); 
    }   
    handleChange = source => {    
        this.setState({value: source.value});   
    }; 
}
  • 试想一下,如果组件在第一次渲染的时候被中断,因为组件没有实现渲染,所以并不会执行 componentWillUnmount 生命周期(注:很多人常常认为 componentWillMount 和 componentWillUnmount 总是配对,但这并不是肯定的。只有调用 componentDidMount 后,React 能力保障稍后调用 componentWillUnmount 进行清理)。因而 handleSubscriptionChange 还是会在数据返回胜利后被执行,这时候 setState 因为组件曾经被移除,就会导致内存透露。所以倡议把异步获取内部数据写在 componentDidMount 生命周期里,这样就能保障 componentWillUnmount 生命周期会在组件移除的时候被执行,防止内存透露的危险。
  • 当初,小伙伴分明为什么了要用 UNSAFE_componentWillMount 替换 componentWillMount 了吧

componentWillReceiveProps

componentWillReceiveProps 生命周期是在 props 更新时触发。个别用于 props 参数更新时同步更新 state 参数。但如果在 componentWillReceiveProps 生命周期间接调用父组件的某些有调用 setState 的函数,会导致程序死循环

// 如下是子组件 componentWillReceiveProps 里调用父组件扭转 state 的函数示例

class Parent extends React.Component{constructor(){super();
        this.state={list: [],
            selectedData: {}};
    }

    changeSelectData = selectedData => {
        this.setState({selectedData});
    }

    render(){
        return (<Clild list={this.state.list} changeSelectData={this.changeSelectData}/>
        );
    }
}

...
class Child extends React.Component{constructor(){super();
        this.state={list: []
        };
    }
    componentWillReceiveProps(nextProps){
        this.setState({list: nextProps.list})
        nextProps.changeSelectData(nextProps.list[0]); // 默认抉择第一个
    }
    ...
}
  • 如上代码,在 Child 组件的 componentWillReceiveProps 里间接调用 Parent 组件的 changeSelectData 去更新 Parent 组件 state 的 selectedData 值。会触发 Parent 组件从新渲染,而 Parent 组件从新渲染会触发 Child 组件的 componentWillReceiveProps 生命周期函数执行。如此就会陷入死循环。导致程序解体。
  • 所以,React 官网把 componentWillReceiveProps 替换为 UNSAFE_componentWillReceiveProps,让小伙伴在应用这个生命周期的时候留神它会有缺点,要留神防止,比方下面例子,Child 在 componentWillReceiveProps 调用 changeSelectData 时先判断 list 是否有更新再确定是否要调用,就能够防止死循环。

componentWillUpdate

componentWillUpdate 生命周期在视图更新前触发。个别用于视图更新前保留一些数据不便视图更新实现后赋值。案例三:如下是列表加载更新后回到以后滚动条地位的案例

class ScrollingList extends React.Component {   
    listRef = null;   
    previousScrollOffset = null;   
    componentWillUpdate(nextProps, nextState) {if (this.props.list.length < nextProps.list.length) {this.previousScrollOffset = this.listRef.scrollHeight - this.listRef.scrollTop;} 
    }   
    componentDidUpdate(prevProps, prevState) {if (this.previousScrollOffset !== null) {      
            this.listRef.scrollTop = this.listRef.scrollHeight - this.previousScrollOffset;  
            this.previousScrollOffset = null;    
        }   
    }   
    render() {    
        return (`<div>` {/* ...contents... */}`</div>`     
        );   
    }   
    setListRef = ref => {this.listRef = ref;};
  • 因为 componentWillUpdate 和 componentDidUpdate 这两个生命周期函数有肯定的时间差(componentWillUpdate 后通过渲染、计算、再更新 DOM 元素,最初才调用 componentDidUpdate),如果这个时间段内用户刚好拉伸了浏览器高度,那 componentWillUpdate 计算的 previousScrollOffset 就不精确了。如果在 componentWillUpdate 进行 setState 操作,会呈现屡次调用只更新一次的问题,把 setState 放在 componentDidUpdate,能保障每次更新只调用一次。
  • 所以,react 官网倡议把 componentWillUpdate 替换为 UNSAFE_componentWillUpdate。如果真的有以上案例的需要,能够应用 16.3 新退出的一个周期函数 getSnapshotBeforeUpdat

论断

  • React 意识到 componentWillMount、componentWillReceiveProps 和 componentWillUpdate 这三个生命周期函数有缺点,比拟容易导致解体。然而因为旧的我的项目曾经在用以及有些老开发者习惯用这些生命周期函数,于是通过给它加 UNSAFE_来揭示用它的人要留神它们的缺点
  • React 退出了两个新的生命周期函数 getSnapshotBeforeUpdate 和 getDerivedStateFromProps,目标为了即便不应用这三个生命周期函数,也能实现只有这三个生命周期能实现的性能

diff 算法是怎么运作

每一种节点类型有本人的属性,也就是 prop,每次进行 diff 的时候,react 会先比拟该节点类型,如果节点类型不一样,那么 react 会间接删除该节点,而后间接创立新的节点插入到其中,如果节点类型一样,那么会比拟 prop 是否有更新,如果有 prop 不一样,那么 react 会断定该节点有更新,那么重渲染该节点,而后在对其子节点进行比拟,一层一层往下,直到没有子节点

正文完
 0