前端面试-说说你对react的理解?有哪些特性?

January 10, 2023

说说你对 react 的理解?有哪些特性?

React 是用于构建用户界面的 Javascript 库,只提供了 UI 层面解决方案,遵循组件设计模式、声明式编程范式和函数式编程概念,使前端应用程序更加高效

特性:JSX 语法、单项数据绑定、虚拟 DOM、声明式编程、Component react 类组件使用一个名为 render() 的方法或者函数组件 return,接收输入的数据并返回需要展示的内容

class HelloMessage extends React.Component { render() { return <div>Hello {this.props.name}</div>; } } ReactDOM.render( <HelloMessage name="Taylor" />, document.getElementById("hello-example") );
javascript
  • 优势

    1.高效灵活

    2.声明式的设计,简单使用

    3.组件式开发,提高代码复用率

    4.单向响应的数据流会比双向绑定的更安全,速度更快


说说 React 生命周期有哪些不同的阶段?每个阶段对应的方法是?

React 生命周期就是包括从创建,初始化数据、编译模板、挂载 DOM、渲染、更新渲染、卸载等一系列的过程

可以划分为三个阶段:创建阶段、更新阶段、卸载阶段

创建阶段: constructor getDerivedStateFromProps render componentDidMount 更新阶段: getDerivedStateFromProps shouldComponentUpdate render getSnapshotBeforeUpdate componentDidUpdate 卸载阶段: componentWillUnmount
javascript

新版生命周期: https://s.test.com/files/webcilj41bvnh8curzb/7f618f9297e946e389a28055aca8c0d9.png 旧的生命周期: https://s.test.com/files/webcilj41d56uvmthqm/0e2c0d196394471bbe448bb42b37ccd2.png

通过两个图的对比,可以发现新版的生命周期减少了以下三种方法:

1.componentWillMount

2.componentWillReceiveProps

3.componentWillUpdate

其实这三个方法仍然存在,只是在前者加上了 UNSAFE_前缀,如 UNSAFE_componentWillMount,

并不像字面意思那样表示不安全,而是表示这些生命周期的代码可能在未来的 react 版本可能废除

同时也新增了两个生命周期函数:

1.getDerivedStateFromProps

2.getSnapshotBeforeUpdate

说说你对 React 中虚拟 dom 的理解?

虚拟 DOM 是一种编程概念,就是一颗虚拟的 Javascript 对象树,它将真实的 dom 转换为一个个 js 对象,保存在内存中

虚拟 DOM 通过 js 模拟网页文档节点,生成虚拟 DOM 树,然后进一步生成真实的 DOM 树,渲染到页面,如果内容发生改变,React 会重新生成一棵全新的虚拟 DOM 树,与前边的旧的虚拟 DOM 进行比较,通过 diff 算法,将变化的部分打包成 patch,再次应用到真实 DOM 中,重新渲染页面。 https://s.test.com/files/webcilj41em44izha7a/fcc1edfe41ace48ca405e10cb7ba102b.png 总结

1.React 通过制定大胆的 diff 策略,将 O(n3) 复杂度的问题转换成 O(n) 复杂度的问题;

2.React 通过分层求异的策略,对 tree diff 进行算法优化;

3.React 通过相同类生成相似树形结构,不同类生成不同树形结构的策略,对 component diff 进行算法优化;

4.React 通过设置唯一 key 的策略,对 element diff 进行算法优化

说说 Real diff 算法是怎么运作的?

Diff 算法是为了节省性能而设计的,diff 算法和虚拟 dom 的结合。

基本流程:在第一次 render 在执行的时候会将第一次的虚拟 DOM 做依次缓存,当第二次渲染时会将新的虚拟 DOM 和旧的虚拟 DOM 进行比较,计算出虚拟 DOM 中真正发生变化的部分,从而值针对变化的部分进行更新渲染,避免造成性能的浪费 https://s.test.com/files/webcilj41fx0zup1m08/a3c40e456f5f6b5ae4791daac4839733.png

说说你对 react hook 的理解?

Hook 是 React16.8 新增特性,可以让你在不编写 class 的情况下使用 state 以及一些其他的 React 特性;函数组件是无状态组件,Hook 使函数组件的功能得到了扩充,拥有了类组件相似的功能,Hook 还拥有代码的复用机制,更加方便灵活。

常见的 Hook:useState()、useEffect()、useReducer()、useCallback()、useMemo()、useRef() useState

首先给出一个例子,如下:

import React, { useState } from "react"; function Example() { // 声明一个叫 "count" 的 state 变量 const [count, setCount] = useState(0); return ( <div> <p>You clicked {count} times</p> <button onClick={() => setCount(count + 1)}>Click me</button> </div> ); }
javascript

在函数组件中通过 useState 实现函数内部维护 state,参数为 state 默认的值,返回值是一个数组,第一个值为当前的 state,第二个值为更新 state 的函数

该函数组件等价于的类组件如下:

class Example extends React.Component { constructor(props) { super(props); this.state = { count: 0, }; } render() { return ( <div> <p>You clicked {this.state.count} times</p> <button onClick={() => this.setState({ count: this.state.count + 1 })}> Click me </button> </div> ); } }
javascript

从上述两种代码分析,可以看出两者区别:

1.state 声明方式:在函数组件中通过 useState 直接获取,类组件通过 constructor 构造函数中设置

2.state 读取方式:在函数组件中直接使用变量,类组件通过 this.state.count 的方式获取

3.state 更新方式:在函数组件中通过 setCount 更新,类组件通过 this.setState()

总的来讲,useState 使用起来更为简洁,减少了 this 指向不明确的情况

React 组件之间如何通信?

React 组件通信就是值组件通过某种方式来传递信息以达到某个目的

方式:

1.父组件向子组件传递信息:由于 react 数据流动是单向的,父组件在调用子组件时,只需要在子组件标签内传递参数,子组件通过 props 属性接收

function EmailInput(props) { return ( <label> Email: <input value={props.email} /> </label> ); } const element = <EmailInput email="123124132@163.com" />;
javascript

2.子组件向父组件传递信息;父组件向子组件传递一个函数,然后通过这个函数的回调,拿到子组件传过来的值

父组件对应代码如下:

class Parents extends Component { constructor() { super(); this.state = { price: 0, }; } getItemPrice(e) { this.setState({ price: e, }); } render() { return ( <div> <div>price: {this.state.price}</div> {/* 向子组件中传入一个函数 */} <Child getPrice={this.getItemPrice.bind(this)} /> </div> ); } }
javascript

子组件对应代码如下:

class Child extends Component { clickGoods(e) { // 在此函数中传入值 this.props.getPrice(e); } render() { return ( <div> <button onClick={this.clickGoods.bind(this, 100)}>goods1</button> <button onClick={this.clickGoods.bind(this, 1000)}>goods2</button> </div> ); } }
javascript

1.兄弟组件之间的传递:父组件作为中间层来实现数据的互通,通过使用父组件传递

class Parent extends React.Component { constructor(props) { super(props); this.state = { count: 0 }; } setCount = () => { this.setState({ count: this.state.count + 1 }); }; render() { return ( <div> <SiblingA count={this.state.count} /> <SiblingB onClick={this.setCount} /> </div> ); } }
javascript

2.父组件向后代组件传值:使用 context 提供的组件通信的一种方式,可以实现数据的共享,Provider 组件通过 value 属性传递给后代组件

3.非关系组件传递数据:将数据进行一个全局的资源管理,从而实现组件间的通信功能,例如 redux


说说你对受控组件和非受控组件的理解?应用场景?

受控组件:简单说就是收到 setState 的控制,组件的状态全程响应外部数据 举个简单的例子:

class TestComponent extends React.Component { constructor(props) { super(props); this.state = { username: "lindaidai" }; } render() { return <input name="username" value={this.state.username} />; } }
javascript

这时候当我们在输入框输入内容的时候,会发现输入的内容并无法显示出来,也就是 input 标签是一个可读的状态

这是因为 value 被 this.state.username 所控制住。当用户输入新的内容时,this.state.username 并不会自动更新,这样的话 input 内的内容也就不会变了

如果想要解除被控制,可以为 input 标签设置 onChange 事件,输入的时候触发事件函数,在函数内部实现 state 的更新,从而导致 input 框的内容页发现改变

因此,受控组件我们一般需要初始状态和一个状态更新事件函数

非受控组件:不受 setState 的控制,一般在初始化的时候接收外部的数据,然后自己在内部存储其自身的状态

import React, { Component } from "react"; export class UnControll extends Component { constructor(props) { super(props); this.inputRef = React.createRef(); } handleSubmit = (e) => { console.log("我们可以获得input内的值为", this.inputRef.current.value); e.preventDefault(); }; render() { return ( <form onSubmit={(e) => this.handleSubmit(e)}> <input defaultValue="lindaidai" ref={this.inputRef} /> <input type="submit" value="提交" /> </form> ); } }
javascript

应用场景:

受控组件:强制输入格式、一个数据的多个输入、动态输入、提交时的验证问题

非受控组件:一次性取值(提交时)、提交时的验证


说说 Connect 组件的原理是什么?

Connect 连接 redux 和 react,包裹在我们容器组件外层,接收上边的 Provider 提供的 store 里 state 和 dispatch,传给一个构造函数,返回一个对象,以属性的形式传递给我们的容器组件

Connect 是一个高阶函数,首先传入 mapStateToProps、mapDispatchToProps,然后返回 Component 函数,然后将真正的 Component 作为参数传入,从而返回一个新的组件


说说 react 中 jsx 语法糖的本质?

Jsx 的本质就是函数 React.createElement 的语法糖,所有的 jsx 语法都会最终经过 babel.js 转化为 React.createElement 这个函数调用

三个参数:type 是指的当前的元素类型,config 是 jsx 属性,以对象的属性和值的形式存储,children 是存放在标签中的内容


说说你对 redux 中间件的理解?常用的中间件有哪些?实现原理?

Redux 中间件就是介于应用系统和系统软件之间的一类软件,使用系统软件提供的基础服务,衔接网络上应用系统的各个部分或者是不同的应用,达到资源共享,功能共享的目的 Alt text 常用的中间件:

redux-thunk 用于异步操作

const getHomeMultidataAction = () => { return (dispatch) => { axios.get("http://xxx.xx.xx.xx/test").then((res) => { const data = res.data.data; dispatch(changeBannersAction(data.banner.list)); dispatch(changeRecommendsAction(data.recommend.list)); }); }; };
javascript

redux-logger 用于日志的记录

import { applyMiddleware, createStore } from "redux"; import createLogger from "redux-logger"; const logger = createLogger(); const store = createStore(reducer, applyMiddleware(logger));
javascript

实现原理:所有中间件被放进一个数组中嵌套执行,判断传递过来的数据类型,最后执行 store.dispatch,中间件内部 middleware API 可以拿到 getstate 和 dispatch 方法。


说说 React jsx 转换成真实 DOM 的过程?

1.使用 React.createElement 或 jsx 编写 react 组件,实际上所有的 jsx 代码最后都会转换成 React.ccreateElement(...),babel 帮助我们完成转换的过程

2.createElement 函数对于 key 和 ref 等特殊的 props 进行处理,并获取 defaultProps 对默认的 props 进行赋值,并且对传入的子节点进行处理,最终构成一个虚拟 DOM 对象

3.ReactDOM.render 将生成好的虚拟 DOM 渲染到指定的容器上,其中采用了批处理,事务等机制并且对特定的浏览器进行了性能优化,最终转换为真实 DOM JSX 通过 babel 最终转化成 React.createElement 这种形式,例如:

<div> <img src="avatar.png" className="profile" /> <Hello /> </div>
javascript

会被 bebel 转化成如下:

React.createElement( "div", null, React.createElement("img", { src: "avatar.png", className: "profile", }), React.createElement(Hello, null) );
javascript

在转化过程中,babel 在编译时会判断 JSX 中组件的首字母:

1.当首字母为小写时,其被认定为原生 DOM 标签,createElement 的第一个变量被编译为字符串

2.当首字母为大写时,其被认定为自定义组件,createElement 的第一个变量被编译为对象

最终都会通过 RenderDOM.render(...)方法进行挂载,如下:

ReactDOM.render(<App />, document.getElementById("root"));
javascript

说说你对@reduxjs/toolkit 的理解?和 react-redux 有什么区别?

React-redux 是官方 react UI 绑定层,允许 React 组件从 redux 存储中读取数据,并将操作分派到存储以更新的状态。提供了 connect,Provider 等 API,帮助我们连接 react 和 redux,实现的逻辑会更加的严谨高效

@reduxjs/tookit 是对 Redux 的二次封装,开箱即用的一个高效的 Redux 开发工具,使得创建 store,更新 store

区别:

  1. reduxjs/tookit 相对于 react-redux 来说比较方便,集成了 redux-devtools-extension,不需要额外的配置,非常方便

2.reduxjs/tookit 集成 immutable-js 的功能,不需要安装配置使用,提升了开发效率

3.reduxjs/tookit 集成了 redux-thunk 的功能,reduxjs/tookit 将 types、actions、reducers 放在一起组成了全新的 slices,简化了我们的使用


React render 方法的原理,在什么时候会触发?

Render 函数在 react 中有两种形式,在类组件中指的是 render 方法,在函数组件中,指的是函数组件本身,在 render 中我们会编写 jsx,jsx 通过 babel 编译后就会转化为我们熟悉的 js 格式,在 render 过程中,React 将新调用的 render 函数返回的树与旧版本的树进行比较,决定如何更新 DOM,然后进行 diff 比较,更新 DOM 树

触发时机:

类组件调用 setState 修改状态时;点击按钮,则调用 setState 方法,无论 count 发生变化辩护,控制台都会输出 Foo render,证明 render 执行了

class Foo extends React.Component { state = { count: 0 }; increment = () => { const { count } = this.state; const newCount = count < 10 ? count + 1 : count; this.setState({ count: newCount }); }; render() { const { count } = this.state; console.log("Foo render"); return ( <div> <h1> {count} </h1> <button onClick={this.increment}>Increment</button> </div> ); } }
javascript

函数组件通过 useState Hook 修改状态时;函数组件通过 useState 这种形式更新数据,当数组的值不发生改变了,就不会触发 render

function Foo() { const [count, setCount] = useState(0); function increment() { const newCount = count < 10 ? count + 1 : count; setCount(newCount); } console.log("Foo render"); return ( <div> <h1> {count} </h1> <button onClick={increment}>Increment</button> </div> ); }
javascript

类组件重新渲染时;只要点击了 App 组件内的 Change name 按钮,不管 Foo 具体实现是什么,都会被重新 render 渲染

class App extends React.Component { state = { name: "App" }; render() { return ( <div className="App"> <Foo /> <button onClick={() => this.setState({ name: "App" })}> Change name </button> </div> ); } } function Foo() { console.log("Foo render"); return ( <div> <h1> Foo </h1> </div> ); }
javascript

函数组件重新渲染时;可以发现,使用 useState 来更新状态的时候,只有首次会触发 Foo render,后面并不会导致 Foo render

function App() { const [name, setName] = useState("App"); return ( <div className="App"> <Foo /> <button onClick={() => setName("aaa")}>{name}</button> </div> ); } function Foo() { console.log("Foo render"); return ( <div> <h1> Foo </h1> </div> ); }
javascript