React面试八股文(第一期)

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 前提示用户进行确认。

posted @ 2022-10-18 08:48  beifeng11996  阅读(63)  评论(0编辑  收藏  举报