关于react.js:腾讯前端经典react面试题汇总

28次阅读

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

概述一下 React 中的事件处理逻辑。

为了解决跨浏览器兼容性问题,React 会将浏览器原生事件(Browser Native Event)封装为合成事件(Synthetic Event)并传入设置的事件处理程序中。
这里的合成事件提供了与原生事件雷同的接口,不过它们屏蔽了底层浏览器的细节差别,保障了行为的一致性。另外,React 并没有间接将事件附着到子元素上,而是以繁多事件监听器的形式将所有的事件发送到顶层进行解决(基于事件委托原理)。
这样 React 在更新 DOM 时就不须要思考如何解决附着在 DOM 上的事件监听器,最终达到优化性能的目标。

如果用索引值作为 key 会呈现什么样的问题

  • 若对数据进行逆序增加,逆序删除等毁坏程序的操作

    则会产生没有必要的实在 DOM 更新,界面想过看不出区别,然而效劳低,性能不好

  • 如果构造中还蕴含输出类的 DOM

    会产生谬误的 DOM 更新 ===》界面会有问题

如果不存在对数据的逆序增加 逆序删除等毁坏程序操作,仅用于渲染展现,用 index 作为 key 也没有问题

react hooks,它带来了那些便当

  • 代码逻辑聚合,逻辑复用
  • HOC 嵌套天堂
  • 代替 class

React 中通常应用 类定义 或者 函数定义 创立组件:

在类定义中,咱们能够应用到许多 React 个性,例如 state、各种组件生命周期钩子等,然而在函数定义中,咱们却无能为力,因而 React 16.8 版本推出了一个新性能 (React Hooks),通过它,能够更好的在函数定义组件中应用 React 个性。

益处:

  1. 跨组件复用: 其实 render props / HOC 也是为了复用,相比于它们,Hooks 作为官网的底层 API,最为轻量,而且革新老本小,不会影响原来的组件层次结构和传说中的嵌套天堂;
  2. 类定义更为简单
  3. 不同的生命周期会使逻辑变得扩散且凌乱,不易保护和治理;
  • 时刻须要关注 this 的指向问题;
  • 代码复用代价高,高阶组件的应用常常会使整个组件树变得臃肿;
  • 状态与 UI 隔离: 正是因为 Hooks 的个性,状态逻辑会变成更小的粒度,并且极容易被形象成一个自定义 Hooks,组件中的状态和 UI 变得更为清晰和隔离。

留神:

  • 防止在 循环 / 条件判断 / 嵌套函数 中调用 hooks,保障调用程序的稳固;
  • 只有 函数定义组件 和 hooks 能够调用 hooks,防止在 类组件 或者 一般函数 中调用;
  • 不能在 useEffect 中应用 useState,React 会报错提醒;
  • 类组件不会被替换或废除,不须要强制革新类组件,两种形式能并存;

重要钩子

  1. 状态钩子 (useState): 用于定义组件的 State,其到类定义中 this.state 的性能;
// useState 只承受一个参数: 初始状态
// 返回的是组件名和更改该组件对应的函数
const [flag, setFlag] = useState(true);
// 批改状态
setFlag(false)

// 下面的代码映射到类定义中:
this.state = {flag: true}
const flag = this.state.flag
const setFlag = (bool) => {
    this.setState({flag: bool,})
}
  1. 生命周期钩子 (useEffect):

类定义中有许多生命周期函数,而在 React Hooks 中也提供了一个相应的函数 (useEffect),这里能够看做 componentDidMount、componentDidUpdate 和 componentWillUnmount 的联合。

useEffect(callback,)承受两个参数

  • callback: 钩子回调函数;
  • source: 设置触发条件,仅当 source 产生扭转时才会触发;
  • useEffect 钩子在没有传入参数时,默认在每次 render 时都会优先调用上次保留的回调中返回的函数,后再从新调用回调;
useEffect(() => {
    // 组件挂载后执行事件绑定
    console.log('on')
    addEventListener()

    // 组件 update 时会执行事件解绑
    return () => {console.log('off')
        removeEventListener()}
}, );


// 每次 source 产生扭转时,执行后果(以类定义的生命周期,便于大家了解):
// --- DidMount ---
// 'on'
// --- DidUpdate ---
// 'off'
// 'on'
// --- DidUpdate ---
// 'off'
// 'on'
// --- WillUnmount --- 
// 'off'

通过第二个参数,咱们便可模拟出几个罕用的生命周期:

  • componentDidMount: 传入 [] 时,就只会在初始化时调用一次
const useMount = (fn) => useEffect(fn, [])
  • componentWillUnmount: 传入[],回调中的返回的函数也只会被最终执行一次
const useUnmount = (fn) => useEffect(() => fn, [])
  • mounted: 能够应用 useState 封装成一个高度可复用的 mounted 状态;
const useMounted = () => {const [mounted, setMounted] = useState(false);
    useEffect(() => {!mounted && setMounted(true);
        return () => setMounted(false);
    }, []);
    return mounted;
}
  • componentDidUpdate: useEffect 每次均会执行,其实就是排除了 DidMount 后即可;
const mounted = useMounted() 
useEffect(() => {mounted && fn()
})
  1. 其它内置钩子:
  2. useContext: 获取 context 对象
  • useReducer: 相似于 Redux 思维的实现,但其并不足以代替 Redux,能够了解成一个组件外部的 redux:

    • 并不是长久化存储,会随着组件被销毁而销毁;
    • 属于组件外部,各个组件是互相隔离的,单纯用它并无奈共享数据;
    • 配合 useContext` 的全局性,能够实现一个轻量级的 Redux;(easy-peasy)
  • useCallback: 缓存回调函数,防止传入的回调每次都是新的函数实例而导致依赖组件从新渲染,具备性能优化的成果;
  • useMemo: 用于缓存传入的 props,防止依赖的组件每次都从新渲染;
  • useRef: 获取组件的实在节点;
  • useLayoutEffect

    • DOM 更新同步钩子。用法与 useEffect 相似,只是区别于执行工夫点的不同
    • useEffect 属于异步执行,并不会期待 DOM 真正渲染后执行,而 useLayoutEffect 则会真正渲染后才触发;
    • 能够获取更新后的 state;
  • 自定义钩子(useXxxxx): 基于 Hooks 能够援用其它 Hooks 这个个性,咱们能够编写自定义钩子,如下面的 useMounted。又例如,咱们须要每个页面自定义题目:
function useTitle(title) {
  useEffect(() => {document.title = title;});
}

// 应用:
function Home() {
    const title = '我是首页'
    useTitle(title)

    return (<div>{title}</div>
    )
}

React Portal 有哪些应用场景

  • 在以前,react 中所有的组件都会位于 #app 下,而应用 Portals 提供了一种脱离 #app 的组件
  • 因而 Portals 适宜脱离文档流(out of flow) 的组件,特地是 position: absolute 与 position: fixed 的组件。比方模态框,告诉,正告,goTop 等。

以下是官网一个模态框的示例,能够在以下地址中测试成果

<html>
  <body>
    <div id="app"></div>
    <div id="modal"></div>
    <div id="gotop"></div>
    <div id="alert"></div>
  </body>
</html>
const modalRoot = document.getElementById('modal');

class Modal extends React.Component {constructor(props) {super(props);
    this.el = document.createElement('div');
  }

  componentDidMount() {modalRoot.appendChild(this.el);
  }

  componentWillUnmount() {modalRoot.removeChild(this.el);
  }

  render() {
    return ReactDOM.createPortal(
      this.props.children,
      this.el,
    );
  }
}

React Hooks 当中的 useEffect 是如何辨别生命周期钩子的

useEffect 能够看成是 componentDidMountcomponentDidUpdatecomponentWillUnmount三者的联合。useEffect(callback,)接管两个参数,调用形式如下

useEffect(() => {console.log('mounted');

   return () => {console.log('willUnmount');
   }
 }, );

生命周期函数的调用次要是通过第二个参数来进行管制,有如下几种状况:

  • 参数不传时,则每次都会优先调用上次保留的函数中返回的那个函数,而后再调用内部那个函数;
  • 参数传 [] 时,则内部的函数只会在初始化时调用一次,返回的那个函数也只会最终在组件卸载时调用一次;
  • 参数有值时,则只会监听到数组中的值发生变化后才优先调用返回的那个函数,再调用内部的函数。

createElement 和 cloneElement 有什么区别?

createElement 是 JSX 被转载失去的,在 React 中用来创立 React 元素(即虚构 DOM)的内容。cloneElement 用于复制元素并传递新的 props。

redux 有什么毛病

  • 一个组件所须要的数据,必须由父组件传过来,而不能像 flux 中间接从 store 取。
  • 当一个组件相干数据更新时,即便父组件不须要用到这个组件,父组件还是会从新 render,可能会有效率影响,或者须要写简单的shouldComponentUpdate 进行判断。

形容事件在 React 中的解决形式。

为了解决跨浏览器兼容性问题,React 中的事件处理程序将传递 SyntheticEvent 的实例,它是跨浏览器事件的包装器。这些 SyntheticEvent 与你习惯的原生事件具备雷同的接口,它们在所有浏览器中都兼容。
React 实际上并没有将事件附加到子节点自身。而是通过事件委托模式,应用单个事件监听器监听顶层的所有事件。这对于性能是有益处的。这也意味着在更新 DOM 时,React 不须要放心跟踪事件监听器。

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

redux 中间件

中间件提供第三方插件的模式,自定义拦挡 action -> reducer 的过程。变为 action -> middlewares -> reducer。这种机制能够让咱们扭转数据流,实现如异步 action,action 过 滤,日志输入,异样报告等性能

常见的中间件:

  • redux-logger: 提供日志输入;
  • redux-thunk: 解决异步操作;
  • redux-promise: 解决异步操作;
  • actionCreator 的返回值是 promise

在 React 中元素(element)和组件(component)有什么区别?

简略地说,在 React 中元素(虛拟 DOM)形容了你在屏幕上看到的 DOM 元素。
换个说法就是,在 React 中元素是页面中 DOM 元素的对象示意形式。在 React 中组件是一个函数或一个类,它能够承受输出并返回一个元素。
留神:工作中,为了进步开发效率,通常应用 JSX 语法示意 React 元素(虚构 DOM)。在编译的时候,把它转化成一个 React. createElement 调用办法。

如何应用 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);

key 的作用

是给每一个 vnode 的惟一 id, 能够依附 key, 更精确, 更快的拿到 oldVnode 中对应的 vnode 节点

<!-- 更新前 -->
<div>
  <p key="ka">ka</p>
  <h3 key="song">song</he>
</div>

<!-- 更新后 -->
<div>
  <h3 key="song">song</h3>
  <p key="ka">ka</p>
</div>

如果没有 key,React 会认为 div 的第一个子节点由 p 变成 h3,第二个子节点由 h3 变成 p,则会销毁这两个节点并从新结构。

然而当咱们用 key 指明了节点前后对应关系后,React 晓得 key === "ka" 的 p 更新后还在,所以能够复用该节点,只须要替换程序。

key 是 React 用来追踪哪些列表元素被批改、被增加或者被移除的辅助标记。

在开发过程中,咱们须要保障某个元素的 key 在其同级元素中具备唯一性。在 React diff 算法中,React 会借助元素的 Key 值来判断该元素是早先创立的还是被挪动而来的元素,从而缩小不必要的元素从新渲染。同时,React 还须要借助 key 来判断元素与本地状态的关联关系。

componentWillReceiveProps 调用机会

  • 曾经被废除掉
  • 当 props 扭转的时候才调用,子组件第二次接管到 props 的时候

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

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

react 旧版生命周期函数

初始化阶段

  • getDefaultProps: 获取实例的默认属性
  • getInitialState: 获取每个实例的初始化状态
  • componentWillMount:组件行将被装载、渲染到页面上
  • render: 组件在这里生成虚构的 DOM 节点
  • componentDidMount: 组件真正在被装载之后

运行中状态

  • componentWillReceiveProps: 组件将要接管到属性的时候调用
  • shouldComponentUpdate: 组件承受到新属性或者新状态的时候(能够返回 false,接收数据后不更新,阻止 render 调用,前面的函数不会被继续执行了)
  • componentWillUpdate: 组件行将更新不能批改属性和状态
  • render: 组件从新描述
  • componentDidUpdate: 组件曾经更新

销毁阶段

  • componentWillUnmount: 组件行将销毁

在 React 中遍历的办法有哪些?

(1)遍历数组:map && forEach

import React from 'react';

class App extends React.Component {render() {let arr = ['a', 'b', 'c', 'd'];
    return (
      <ul>
        {arr.map((item, index) => {return <li key={index}>{item}</li>
          })
        }
      </ul>
    )
  }
}

class App extends React.Component {render() {let arr = ['a', 'b', 'c', 'd'];
    return (
      <ul>
        {arr.forEach((item, index) => {return <li key={index}>{item}</li>
          })
        }
      </ul>
    )
  }
}

(2)遍历对象:map && for in

class App extends React.Component {render() {
    let obj = {
      a: 1,
      b: 2,
      c: 3
    }
    return (
      <ul>
        {(() => {let domArr = [];
            for(const key in obj) {if(obj.hasOwnProperty(key)) {const value = obj[key]
                domArr.push(<li key={key}>{value}</li>)
              }
            }
            return domArr;
          })()}
      </ul>
    )
  }
}

// Object.entries() 把对象转换成数组
class App extends React.Component {render() {
    let obj = {
      a: 1,
      b: 2,
      c: 3
    }
    return (
      <ul>
        {Object.entries(obj).map(([key, value], index) => {// item 是一个数组,把 item 解构,写法是[key, value]
            return <li key={key}>{value}</li>
          }) 
        }
      </ul>
    )
  }
}

React-Router 的实现原理是什么?

客户端路由实现的思维:

  • 基于 hash 的路由:通过监听 hashchange 事件,感知 hash 的变动

    • 扭转 hash 能够间接通过 location.hash=xxx
  • 基于 H5 history 路由:

    • 扭转 url 能够通过 history.pushState 和 resplaceState 等,会将 URL 压入堆栈,同时可能利用 history.go() 等 API
    • 监听 url 的变动能够通过自定义事件触发实现

react-router 实现的思维:

  • 基于 history 库来实现上述不同的客户端路由实现思维,并且可能保留历史记录等,磨平浏览器差别,下层无感知
  • 通过保护的列表,在每次 URL 发生变化的回收,通过配置的 路由门路,匹配到对应的 Component,并且 render

React 16.X 中 props 扭转后在哪个生命周期中解决

在 getDerivedStateFromProps 中进行解决。

这个生命周期函数是为了代替 componentWillReceiveProps 存在的,所以在须要应用 componentWillReceiveProps 时,就能够思考应用 getDerivedStateFromProps 来进行代替。

两者的参数是不雷同的,而 getDerivedStateFromProps 是一个动态函数,也就是这个函数不能通过 this 拜访到 class 的属性,也并不举荐间接拜访属性。而是应该通过参数提供的 nextProps 以及 prevState 来进行判断,依据新传入的 props 来映射到 state。

须要留神的是,如果 props 传入的内容不须要影响到你的 state,那么就须要返回一个 null,这个返回值是必须的,所以尽量将其写到函数的开端:

static getDerivedStateFromProps(nextProps, prevState) {const {type} = nextProps;
    // 当传入的 type 发生变化的时候,更新 state
    if (type !== prevState.type) {
        return {type,};
    }
    // 否则,对于 state 不进行任何操作
    return null;
}

为什么虚构 dom 会进步性能

虚构 dom 相当于在 js 和实在 dom 两头加了一个缓存,利用 dom diff 算法防止了没有必要 的 dom 操作,从而进步性能

具体实现步骤如下:

  1. 用 JavaScript 对象构造示意 DOM 树的构造; 而后用这个树构建一个真正的 DOM 树,插到文档当中;
  2. 当状态变更的时候,从新结构一棵新的对象树。而后用新的树和旧的树进行比拟,记 录两棵树差别;
  3. 把 2 所记录的差别利用到步骤 1 所构建的真正的 DOM 树上,视图就更新了。

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.")
    );
  }
}

React 中 setState 的第二个参数作用是什么?

setState 的第二个参数是一个可选的回调函数。这个回调函数将在组件从新渲染后执行。等价于在 componentDidUpdate 生命周期内执行。通常倡议应用 componentDidUpdate 来代替此形式。在这个回调函数中你能够拿到更新后 state 的值:

this.setState({
    key1: newState1,
    key2: newState2,
    ...
}, callback) // 第二个参数是 state 更新实现后的回调函数

正文完
 0