写点什么

React 面试八股文(第一期)

作者:beifeng1996
  • 2022-10-18
    浙江
  • 本文字数:7637 字

    阅读完需:约 1 分钟

react 有什么特点

  • react 使用过的虚拟 DOM,而不是真实 DOM

  • react 可以用服务器渲染

  • react 遵循单向数据流 或者数据绑定

React 数据持久化有什么实践吗?

封装数据持久化组件:


let storage={    // 增加    set(key, value){        localStorage.setItem(key, JSON.stringify(value));    },    // 获取    get(key){        return JSON.parse(localStorage.getItem(key));    },    // 删除    remove(key){        localStorage.removeItem(key);    }};export default Storage;
复制代码


在 React 项目中,通过 redux 存储全局数据时,会有一个问题,如果用户刷新了网页,那么通过 redux 存储的全局数据就会被全部清空,比如登录信息等。这时就会有全局数据持久化存储的需求。首先想到的就是 localStorage,localStorage 是没有时间限制的数据存储,可以通过它来实现数据的持久化存储。


但是在已经使用 redux 来管理和存储全局数据的基础上,再去使用 localStorage 来读写数据,这样不仅是工作量巨大,还容易出错。那么有没有结合 redux 来达到持久数据存储功能的框架呢?当然,它就是 redux-persist。redux-persist 会将 redux 的 store 中的数据缓存到浏览器的 localStorage 中。其使用步骤如下:


(1)首先要安装 redux-persist:


npm i redux-persist
复制代码


(2)对于 reducer 和 action 的处理不变,只需修改 store 的生成代码,修改如下:


import {createStore} from 'redux'import reducers from '../reducers/index'import {persistStore, persistReducer} from 'redux-persist';import storage from 'redux-persist/lib/storage';import autoMergeLevel2 from 'redux-persist/lib/stateReconciler/autoMergeLevel2';const persistConfig = {    key: 'root',    storage: storage,    stateReconciler: autoMergeLevel2 // 查看 'Merge Process' 部分的具体情况};const myPersistReducer = persistReducer(persistConfig, reducers)const store = createStore(myPersistReducer)export const persistor = persistStore(store)export default store
复制代码


(3)在 index.js 中,将 PersistGate 标签作为网页内容的父标签:


import React from 'react';import ReactDOM from 'react-dom';import {Provider} from 'react-redux'import store from './redux/store/store'import {persistor} from './redux/store/store'import {PersistGate} from 'redux-persist/lib/integration/react';ReactDOM.render(<Provider store={store}>            <PersistGate loading={null} persistor={persistor}>                {/*网页内容*/}            </PersistGate>        </Provider>, document.getElementById('root'));
复制代码


这就完成了通过 redux-persist 实现 React 持久化本地数据存储的简单应用。

React 生命周期函数

挂载阶段


挂载阶段也可以理解为初始化阶段,也就是把我们的组件插入到 DOM 中。


  • constructor

  • getDerivedStateFromProps

  • ~~UNSAFE_componentWillMount~~

  • render

  • (React Updates DOM and refs)

  • componentDidMount


  1. constructor


组件的构造函数,第一个被执行。显式定义构造函数时,需要在第一行执行 super(props),否则不能再构造函数中拿到 this


在构造函数中,我们一般会做两件事:


  • 初始化 state

  • 对自定义方法进行 this 绑定


  1. getDerivedStateFromProps


是一个静态函数,所以不能在这里使用 this,也表明了 React 官方不希望调用方滥用这个生命周期函数。每当父组件引发当前组件的渲染过程时,getDerivedStateFromProps 都会被调用,这样我们有机会根据新的 props 和当前的 state 来调整一个新的 state。


这个函数会在收到新的 props,调用了 setState 或 forceUpdate 时被调用。


  1. render


React 最核心的方法,class 组件中必须实现的方法。


当 render 被调用时,它会检查 this.propsthis.state 的变化并返回一下类型之一:


  • 原生的 DOM,如 div

  • React 组件

  • 数组或 Fragment

  • Portals(传送门)

  • 字符串或数字,被渲染成文本节点

  • 布尔值或 null,不会渲染任何东西


  1. componentDidMount


在组件挂载之后立即调用。依赖于 DOM 节点的初始化应该放在这里。如需通过网络请求获取数据,此处是实例化请求的好地方。这个方法比较适合添加订阅的地方,如果添加了订阅,请记得在卸载的时候取消订阅。


你可以在 componentDidMount 里面直接调用 setState,它将触发额外渲染,但此渲染会发生在浏览器更新屏幕之前,如此保证了即使 render 了两次,用户也不会看到中间状态。


更新阶段


更新阶段是指当组件的 props 发生了改变,或者组件内部调用了 setState 或者发生了 forceUpdate,这个阶段的过程包括:


  • UNSAFE_componentWillReceiveProps

  • getDerivedStateFromProps

  • sholdComponentUpdate

  • UNSAFE_componentWIllUpdate

  • render

  • getSnapshotBeforeUpdate

  • (React Updates DOM and refs)

  • componentDidUpdate


  1. shouldComponentUpdate


它有两个参数,根据此函数的返回值来判断是否重新进行渲染,首次渲染或者是当我们调用了 forceUpdate 时并不会触发此方法,此方法仅用于性能优化。


但是官方提倡我们使用内置的 PureComponent 而不是自己编写 shouldComponentUpdate。


  1. getSnapshotBeforeUpdate


这个生命周期函数发生在 render 之后,在更新之前,给了一个机会去获取 DOM 信息,计算得到并返回一个 snapshot,这个 snapshot 会作为 componentDidUpdate 第三个参数传入。


  1. componentDidUpdate


这个函数会在更新后被立即调用,首次渲染不会执行此方法。在这个函数中我们可以操作 DOM,可以发起请求,还可以 setState,但注意一定要用条件语句,否则会导致无限循环。


卸载阶段


  1. componentWillUnmount


这个生命周期函数会在组件卸载销毁之前被调用,我们可以在这里执行一些清除操作。不要在这里调用 setState,因为组件不会重新渲染。

对 Redux 的理解,主要解决什么问题

React 是视图层框架。Redux 是一个用来管理数据状态和 UI 状态的 JavaScript 应用工具。随着 JavaScript 单页应用(SPA)开发日趋复杂, JavaScript 需要管理比任何时候都要多的 state(状态), Redux 就是降低管理难度的。(Redux 支持 React、Angular、jQuery 甚至纯 JavaScript)。


在 React 中,UI 以组件的形式来搭建,组件之间可以嵌套组合。但 React 中组件间通信的数据流是单向的,顶层组件可以通过 props 属性向下层组件传递数据,而下层组件不能向上层组件传递数据,兄弟组件之间同样不能。这样简单的单向数据流支撑起了 React 中的数据可控性。


当项目越来越大的时候,管理数据的事件或回调函数将越来越多,也将越来越不好管理。管理不断变化的 state 非常困难。如果一个 model 的变化会引起另一个 model 变化,那么当 view 变化时,就可能引起对应 model 以及另一个 model 的变化,依次地,可能会引起另一个 view 的变化。直至你搞不清楚到底发生了什么。state 在什么时候,由于什么原因,如何变化已然不受控制。 当系统变得错综复杂的时候,想重现问题或者添加新功能就会变得举步维艰。如果这还不够糟糕,考虑一些来自前端开发领域的新需求,如更新调优、服务端渲染、路由跳转前请求数据等。state 的管理在大项目中相当复杂。


Redux 提供了一个叫 store 的统一仓储库,组件通过 dispatch 将 state 直接传入 store,不用通过其他的组件。并且组件通过 subscribe 从 store 获取到 state 的改变。使用了 Redux,所有的组件都可以从 store 中获取到所需的 state,他们也能从 store 获取到 state 的改变。这比组件之间互相传递数据清晰明朗的多。


主要解决的问题: 单纯的 Redux 只是一个状态机,是没有 UI 呈现的,react- redux 作用是将 Redux 的状态机和 React 的 UI 呈现绑定在一起,当你 dispatch action 改变 state 的时候,会自动更新页面。

redux 有什么缺点

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

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

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

通常,使用 Webpack 的 DefinePlugin 方法将 NODE ENV 设置为 production。这将剥离 propType 验证和额外的警告。除此之外,还可以减少代码,因为 React 使用 Uglify 的 dead-code 来消除开发代码和注释,这将大大减少包占用的空间。

setState 方法的第二个参数有什么用?使用它的目的是什么?

它是一个回调函数,当 setState 方法执行结束并重新渲染该组件时调用它。在工作中,更好的方式是使用 React 组件生命周期之——“存在期”的生命周期方法,而不是依赖这个回调函数。


export class App extends Component {  constructor(props) {    super(props);    this.state = {      username: "雨夜清荷",    };  }  render() {    return <div> {this.state.username}</div>;  }  componentDidMount() {    this.setstate(      {        username: "有课前端网",      },      () => console.log("re-rendered success. ")    );  }}
复制代码

在 Redux 中使用 Action 要注意哪些问题?

在 Redux 中使用 Action 的时候, Action 文件里尽量保持 Action 文件的纯净,传入什么数据就返回什么数据,最妤把请求的数据和 Action 方法分离开,以保持 Action 的纯净。

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

  • 根据组件的职责通常把组件分为 UI 组件和容器组件。

  • UI 组件负责 UI 的呈现,容器组件负责管理数据和逻辑。

  • 两者通过React-Redux 提供connect方法联系起来


参考:前端react面试题详细解答

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

第二个参数是一个函数,该函数会在 setState 函数调用完成并且组件开始重渲染时调用,可以用该函数来监听渲染是否完成。


this.setstate(  {    username: "有课前端网",  },  () => console.log("re-rendered success. "));
复制代码

React 如何判断什么时候重新渲染组件?

组件状态的改变可以因为props的改变,或者直接通过setState方法改变。组件获得新的状态,然后 React 决定是否应该重新渲染组件。只要组件的 state 发生变化,React 就会对组件进行重新渲染。这是因为 React 中的shouldComponentUpdate方法默认返回true,这就是导致每次更新都重新渲染的原因。


当 React 将要渲染组件时会执行shouldComponentUpdate方法来看它是否返回true(组件应该更新,也就是重新渲染)。所以需要重写shouldComponentUpdate方法让它根据情况返回true或者false来告诉 React 什么时候重新渲染什么时候跳过重新渲染。

React 的 Fiber 工作原理,解决了什么问题

  • React Fiber 是一种基于浏览器的单线程调度算法。


React Fiber 用类似 requestIdleCallback 的机制来做异步 diff。但是之前数据结构不支持这样的实现异步 diff,于是 React 实现了一个类似链表的数据结构,将原来的 递归 diff 变成了现在的 遍历 diff,这样就能做到异步可更新了

react-router 里的<Link>标签和<a>标签有什么区别

对比<a>,Link组件避免了不必要的重渲染

React 中什么是受控组件和非控组件?

(1)受控组件 在使用表单来收集用户输入时,例如<input><select><textearea>等元素都要绑定一个 change 事件,当表单的状态发生变化,就会触发 onChange 事件,更新组件的 state。这种组件在 React 中被称为受控组件,在受控组件中,组件渲染出的状态与它的 value 或 checked 属性相对应,react 通过这种方式消除了组件的局部状态,使整个状态可控。react 官方推荐使用受控表单组件。


受控组件更新 state 的流程:


  • 可以通过初始 state 中设置表单的默认值

  • 每当表单的值发生变化时,调用 onChange 事件处理器

  • 事件处理器通过事件对象 e 拿到改变后的状态,并更新组件的 state

  • 一旦通过 setState 方法更新 state,就会触发视图的重新渲染,完成表单组件的更新


受控组件缺陷: 表单元素的值都是由 React 组件进行管理,当有多个输入框,或者多个这种组件时,如果想同时获取到全部的值就必须每个都要编写事件处理函数,这会让代码看着很臃肿,所以为了解决这种情况,出现了非受控组件。


(2)非受控组件 如果一个表单组件没有 value props(单选和复选按钮对应的是 checked props)时,就可以称为非受控组件。在非受控组件中,可以使用一个 ref 来从 DOM 获得表单值。而不是为每个状态更新编写一个事件处理程序。


React 官方的解释:


要编写一个非受控组件,而不是为每个状态更新都编写数据处理函数,你可以使用 ref 来从 DOM 节点中获取表单数据。因为非受控组件将真实数据储存在 DOM 节点中,所以在使用非受控组件时,有时候反而更容易同时集成 React 和非 React 代码。如果你不介意代码美观性,并且希望快速编写代码,使用非受控组件往往可以减少你的代码量。否则,你应该使用受控组件。


例如,下面的代码在非受控组件中接收单个属性:


class NameForm extends React.Component {  constructor(props) {    super(props);    this.handleSubmit = this.handleSubmit.bind(this);  }  handleSubmit(event) {    alert('A name was submitted: ' + this.input.value);    event.preventDefault();  }  render() {    return (      <form onSubmit={this.handleSubmit}>        <label>          Name:          <input type="text" ref={(input) => this.input = input} />        </label>        <input type="submit" value="Submit" />      </form>    );  }}
复制代码


总结: 页面中所有输入类的 DOM 如果是现用现取的称为非受控组件,而通过 setState 将输入的值维护到了 state 中,需要时再从 state 中取出,这里的数据就受到了 state 的控制,称为受控组件。

说说你用 react 有什么坑点?

1. JSX 做表达式判断时候,需要强转为 boolean 类型


如果不使用 !!b 进行强转数据类型,会在页面里面输出 0


render() {  const b = 0;  return <div>    {      !!b && <div>这是一段文本</div>    }  </div>}
复制代码


2. 尽量不要在 componentWillReviceProps 里使用 setState,如果一定要使用,那么需要判断结束条件,不然会出现无限重渲染,导致页面崩溃


3. 给组件添加 ref 时候,尽量不要使用匿名函数,因为当组件更新的时候,匿名函数会被当做新的 prop 处理,让 ref 属性接受到新函数的时候,react 内部会先清空 ref,也就是会以 null 为回调参数先执行一次 ref 这个 props,然后在以该组件的实例执行一次 ref,所以用匿名函数做 ref 的时候,有的时候去 ref 赋值后的属性会取到 null


4. 遍历子节点的时候,不要用 index 作为组件的 key 进行传入

如何有条件地向 React 组件添加属性?

对于某些属性,React 非常聪明,如果传递给它的值是虚值,可以省略该属性。例如:


var InputComponent = React.createClass({  render: function () {    var required = true;    var disabled = false;    return <input type="text" disabled={disabled} required={required} />;  },});
复制代码


渲染结果:


<input type="text" required>
复制代码


另一种可能的方法是:


var condition = true;var component = <div value="foo" {...(condition && { disabled: true })} />;
复制代码

HOC 相比 mixins 有什么优点?

HOC 和 Vue 中的 mixins 作用是一致的,并且在早期 React 也是使用 mixins 的方式。但是在使用 class 的方式创建组件以后,mixins 的方式就不能使用了,并且其实 mixins 也是存在一些问题的,比如:


  • 隐含了一些依赖,比如我在组件中写了某个 state 并且在 mixin 中使用了,就这存在了一个依赖关系。万一下次别人要移除它,就得去 mixin 中查找依赖

  • 多个 mixin 中可能存在相同命名的函数,同时代码组件中也不能出现相同命名的函数,否则就是重写了,其实我一直觉得命名真的是一件麻烦事。。

  • 雪球效应,虽然我一个组件还是使用着同一个 mixin,但是一个 mixin 会被多个组件使用,可能会存在需求使得 mixin 修改原本的函数或者新增更多的函数,这样可能就会产生一个维护成本


HOC 解决了这些问题,并且它们达成的效果也是一致的,同时也更加的政治正确(毕竟更加函数式了)。

当调用setState时,React render 是如何工作的?

咱们可以将"render"分为两个步骤:


  1. 虚拟 DOM 渲染:当render方法被调用时,它返回一个新的组件的虚拟 DOM 结构。当调用setState()时,render会被再次调用,因为默认情况下shouldComponentUpdate总是返回true,所以默认情况下 React 是没有优化的。

  2. 原生 DOM 渲染:React 只会在虚拟 DOM 中修改真实 DOM 节点,而且修改的次数非常少——这是很棒的 React 特性,它优化了真实 DOM 的变化,使 React 变得更快。

React-Router 4 的 Switch 有什么用?

Switch 通常被用来包裹 Route,用于渲染与路径匹配的第一个子 <Route><Redirect>,它里面不能放其他元素。


假如不加 <Switch>


import { Route } from 'react-router-dom'
<Route path="/" component={Home}></Route><Route path="/login" component={Login}></Route>
复制代码


Route 组件的 path 属性用于匹配路径,因为需要匹配 /Home,匹配 /loginLogin,所以需要两个 Route,但是不能这么写。这样写的话,当 URL 的 path 为 “/login” 时,<Route path="/" /><Route path="/login" /> 都会被匹配,因此页面会展示 Home 和 Login 两个组件。这时就需要借助 <Switch> 来做到只显示一个匹配组件:


import { Switch, Route} from 'react-router-dom'
<Switch> <Route path="/" component={Home}></Route> <Route path="/login" component={Login}></Route></Switch>
复制代码


此时,再访问 “/login” 路径时,却只显示了 Home 组件。这是就用到了 exact 属性,它的作用就是精确匹配路径,经常与<Switch> 联合使用。只有当 URL 和该 <Route> 的 path 属性完全一致的情况下才能匹配上:


import { Switch, Route} from 'react-router-dom'
<Switch> <Route exact path="/" component={Home}></Route> <Route exact path="/login" component={Login}></Route></Switch>
复制代码

react router

import React from 'react'import { render } from 'react-dom'import { browserHistory, Router, Route, IndexRoute } from 'react-router'
import App from '../components/App'import Home from '../components/Home'import About from '../components/About'import Features from '../components/Features'
render( <Router history={browserHistory}> // history 路由 <Route path='/' component={App}> <IndexRoute component={Home} /> <Route path='about' component={About} /> <Route path='features' component={Features} /> </Route> </Router>, document.getElementById('app'))render( <Router history={browserHistory} routes={routes} />, document.getElementById('app'))
复制代码


React Router 提供一个 routerWillLeave 生命周期钩子,这使得 React 组件可以拦截正在发生的跳转,或在离开 route 前提示用户。routerWillLeave 返回值有以下两种:


return false 取消此次跳转


return 返回提示信息,在离开 route 前提示用户进行确认。


用户头像

beifeng1996

关注

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

还未添加个人简介

评论

发布
暂无评论
React面试八股文(第一期)_React_beifeng1996_InfoQ写作社区