写点什么

腾讯前端必会 react 面试题合集

作者:xiaofeng
  • 2023-02-27
    浙江
  • 本文字数:7489 字

    阅读完需:约 25 分钟

React-Router 的路由有几种模式?

React-Router 支持使用 hash(对应 HashRouter)和 browser(对应 BrowserRouter) 两种路由规则, react-router-dom 提供了 BrowserRouter 和 HashRouter 两个组件来实现应用的 UI 和 URL 同步:


  • BrowserRouter 创建的 URL 格式:xxx.com/path

  • HashRouter 创建的 URL 格式:xxx.com/#/path


(1)BrowserRouter


它使用 HTML5 提供的 history API(pushState、replaceState 和 popstate 事件)来保持 UI 和 URL 的同步。由此可以看出,BrowserRouter 是使用 HTML 5 的 history API 来控制路由跳转的:


<BrowserRouter    basename={string}    forceRefresh={bool}    getUserConfirmation={func}    keyLength={number}/>
复制代码


其中的属性如下:


  • basename 所有路由的基准 URL。basename 的正确格式是前面有一个前导斜杠,但不能有尾部斜杠;


<BrowserRouter basename="/calendar">    <Link to="/today" /></BrowserRouter>
复制代码


等同于


<a href="/calendar/today" />
复制代码


  • forceRefresh 如果为 true,在导航的过程中整个页面将会刷新。一般情况下,只有在不支持 HTML5 history API 的浏览器中使用此功能;

  • getUserConfirmation 用于确认导航的函数,默认使用 window.confirm。例如,当从 /a 导航至 /b 时,会使用默认的 confirm 函数弹出一个提示,用户点击确定后才进行导航,否则不做任何处理;


// 这是默认的确认函数const getConfirmation = (message, callback) => {  const allowTransition = window.confirm(message);  callback(allowTransition);}<BrowserRouter getUserConfirmation={getConfirmation} />
复制代码


需要配合<Prompt> 一起使用。


  • KeyLength 用来设置 Location.Key 的长度。


(2)HashRouter


使用 URL 的 hash 部分(即 window.location.hash)来保持 UI 和 URL 的同步。由此可以看出,HashRouter 是通过 URL 的 hash 属性来控制路由跳转的:


<HashRouter    basename={string}    getUserConfirmation={func}    hashType={string}  />
复制代码


其参数如下


  • basename, getUserConfirmation 和 BrowserRouter 功能一样;

  • hashType window.location.hash 使用的 hash 类型,有如下几种:

  • slash - 后面跟一个斜杠,例如 #/ 和 #/sunshine/lollipops;

  • noslash - 后面没有斜杠,例如 # 和 #sunshine/lollipops;

  • hashbang - Google 风格的 ajax crawlable,例如 #!/ 和 #!/sunshine/lollipops。

如何使用 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);
复制代码

componentWillReceiveProps 调用时机

  • 已经被废弃掉

  • 当 props 改变的时候才调用,子组件第二次接收到 props 的时候

redux 中间件

中间件提供第三方插件的模式,自定义拦截 action -> reducer 的过程。变为 action -> middlewares -> reducer。这种机制可以让我们改变数据流,实现如异步actionaction 过滤,日志输出,异常报告等功能


  • redux-logger:提供日志输出

  • redux-thunk:处理异步操作

  • redux-promise:处理异步操作,actionCreator的返回值是promise

为什么虚拟 dom 会提高性能

虚拟dom相当于在js和真实dom中间加了一个缓存,利用dom diff算法避免了没有必要的dom操作,从而提高性能


具体实现步骤如下


  • JavaScript 对象结构表示 DOM 树的结构;然后用这个树构建一个真正的 DOM 树,插到文档当中

  • 当状态变更的时候,重新构造一棵新的对象树。然后用新的树和旧的树进行比较,记录两棵树差异

  • 把 2 所记录的差异应用到步骤 1 所构建的真正的DOM树上,视图就更新


虚拟 DOM 一定会提高性能吗?


很多人认为虚拟 DOM 一定会提高性能,一定会更快,其实这个说法有点片面,因为虚拟 DOM 虽然会减少 DOM 操作,但也无法避免 DOM 操作


  • 它的优势是在于 diff 算法和批量处理策略,将所有的 DOM 操作搜集起来,一次性去改变真实的 DOM,但在首次渲染上,虚拟 DOM 会多了一层计算,消耗一些性能,所以有可能会比 html 渲染的要慢

  • 注意,虚拟 DOM 实际上是给我们找了一条最短,最近的路径,并不是说比 DOM 操作的更快,而是路径最简单

在 ReactNative 中,如何解决 adb devices 找不到连接设备的问题?

在使用 Genymotion 时,首先需要在 SDK 的 platform-tools 中加入环境变量,然后在 Genymotion 中单击 Setting,选择 ADB 选项卡,单击 Use custom Android SDK tools,浏览本地 SDK 的位置,单击 OK 按钮就可以了。启动虛拟机后,在 cmd 中输入 adb devices 可以查看设备。


参考 前端进阶面试题详细解答

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

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


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

为什么 JSX 中的组件名要以大写字母开头

因为 React 要知道当前渲染的是组件还是 HTML 元素

受控组件和非受控组件区别是啥?

  • 受控组件是 React 控制中的组件,并且是表单数据真实的唯一来源。

  • 非受控组件是由 DOM 处理表单数据的地方,而不是在 React 组件中。尽管非受控组件通常更易于实现,因为只需使用refs即可从 DOM 中获取值,但通常建议优先选择受控制的组件,而不是非受控制的组件。这样做的主要原因是受控组件支持即时字段验证,允许有条件地禁用/启用按钮,强制输入格式。

Fiber

React 的核心流程可以分为两个部分:


  • reconciliation (调度算法,也可称为 render)

  • 更新 stateprops

  • 调用生命周期钩子;

  • 生成 virtual dom

  • 这里应该称为 Fiber Tree 更为符合;

  • 通过新旧 vdom 进行 diff 算法,获取 vdom change

  • 确定是否需要重新渲染

  • commit

  • 如需要,则操作 dom 节点更新


要了解 Fiber,我们首先来看为什么需要它


  • 问题 : 随着应用变得越来越庞大,整个更新渲染的过程开始变得吃力,大量的组件渲染会导致主进程长时间被占用,导致一些动画或高频操作出现卡顿和掉帧的情况。而关键点,便是 同步阻塞。在之前的调度算法中,React 需要实例化每个类组件,生成一颗组件树,使用 同步递归 的方式进行遍历渲染,而这个过程最大的问题就是无法 暂停和恢复。

  • 解决方案: 解决同步阻塞的方法,通常有两种: 异步 与 任务分割。而 React Fiber 便是为了实现任务分割而诞生的

  • 简述

  • React V16 将调度算法进行了重构, 将之前的 stack reconciler 重构成新版的 fiber reconciler,变成了具有链表和指针的 单链表树遍历算法。通过指针映射,每个单元都记录着遍历当下的上一步与下一步,从而使遍历变得可以被暂停和重启

  • 这里我理解为是一种 任务分割调度算法,主要是 将原先同步更新渲染的任务分割成一个个独立的 小任务单位,根据不同的优先级,将小任务分散到浏览器的空闲时间执行,充分利用主进程的事件循环机制

  • 核心

  • Fiber 这里可以具象为一个 数据结构


class Fiber {    constructor(instance) {        this.instance = instance        // 指向第一个 child 节点        this.child = child        // 指向父节点        this.return = parent        // 指向第一个兄弟节点        this.sibling = previous    }    }
复制代码


  • 链表树遍历算法 : 通过 节点保存与映射,便能够随时地进行 停止和重启,这样便能达到实现任务分割的基本前提

  • 首先通过不断遍历子节点,到树末尾;

  • 开始通过 sibling 遍历兄弟节点;

  • return 返回父节点,继续执行 2;

  • 直到 root 节点后,跳出遍历;

  • 任务分割 ,React 中的渲染更新可以分成两个阶段

  • reconciliation 阶段 : vdom 的数据对比,是个适合拆分的阶段,比如对比一部分树后,先暂停执行个动画调用,待完成后再回来继续比对

  • Commit 阶段 : 将 change list 更新到 dom 上,并不适合拆分,才能保持数据与 UI 的同步。否则可能由于阻塞 UI 更新,而导致数据更新和 UI 不一致的情况

  • 分散执行: 任务分割后,就可以把小任务单元分散到浏览器的空闲期间去排队执行,而实现的关键是两个新 API: requestIdleCallbackrequestAnimationFrame

  • 低优先级的任务交给requestIdleCallback处理,这是个浏览器提供的事件循环空闲期的回调函数,需要 pollyfill,而且拥有 deadline 参数,限制执行事件,以继续切分任务;

  • 高优先级的任务交给requestAnimationFrame处理;


// 类似于这样的方式requestIdleCallback((deadline) => {    // 当有空闲时间时,我们执行一个组件渲染;    // 把任务塞到一个个碎片时间中去;    while ((deadline.timeRemaining() > 0 || deadline.didTimeout) && nextComponent) {        nextComponent = performWork(nextComponent);    }});
复制代码


  • 优先级策略: 文本框输入 > 本次调度结束需完成的任务 > 动画过渡 > 交互反馈 > 数据更新 > 不会显示但以防将来会显示的任务


  • Fiber 其实可以算是一种编程思想,在其它语言中也有许多应用(Ruby Fiber)。

  • 核心思想是 任务拆分和协同,主动把执行权交给主线程,使主线程有时间空挡处理其他高优先级任务。

  • 当遇到进程阻塞的问题时,任务分割、异步调用 和 缓存策略 是三个显著的解决思路。

react-router4 的核心

  • 路由变成了组件

  • 分散到各个页面,不需要配置 比如<link> <route></route>

React 中 keys 的作用是什么?

Keys 是 React 用于追踪哪些列表中元素被修改、被添加或者被移除的辅助标识。


在 React 中渲染集合时,向每个重复的元素添加关键字对于帮助 React 跟踪元素与数据之间的关联非常重要。key 应该是唯一 ID,最好是 UUID 或收集项中的其他唯一字符串:


<ul>  {todos.map((todo) =>    <li key={todo.id}>      {todo.text}    </li>  )};</ul>
复制代码


在集合中添加和删除项目时,不使用键或将索引用作键会导致奇怪的行为。

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

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

调和阶段 setState 内部干了什么

  • 当调用 setState 时,React 会做的第一件事情是将传递给 setState 的对象合并到组件的当前状态

  • 这将启动一个称为和解(reconciliation)的过程。和解(reconciliation)的最终目标是以最有效的方式,根据这个新的状态来更新UI。 为此,React将构建一个新的 React 元素树(您可以将其视为 UI 的对象表示)

  • 一旦有了这个树,为了弄清 UI 如何响应新的状态而改变,React 会将这个新树与上一个元素树相比较( diff )


通过这样做, React 将会知道发生的确切变化,并且通过了解发生什么变化,只需在绝对必要的情况下进行更新即可最小化 UI 的占用空间

React 如何区分 Class 组件 和 Function 组件

一般的方式是借助 typeof 和 Function.prototype.toString 来判断当前是不是 class,如下:


function isClass(func) {  return typeof func === 'function'    && /^class\s/.test(Function.prototype.toString.call(func));}
复制代码


但是这个方式有它的局限性,因为如果用了 babel 等转换工具,将 class 写法全部转为 function 写法,上面的判断就会失效。


React 区分 Class 组件 和 Function 组件的方式很巧妙,由于所有的类组件都要继承 React.Component,所以只要判断原型链上是否有 React.Component 就可以了:


AComponent.prototype instanceof React.Component
复制代码

父子组件的通信方式?

父组件向子组件通信:父组件通过 props 向子组件传递需要的信息。


// 子组件: Childconst Child = props =>{  return <p>{props.name}</p>}// 父组件 Parentconst Parent = ()=>{    return <Child name="react"></Child>}
复制代码


子组件向父组件通信:: props+回调的方式。


// 子组件: Childconst Child = props =>{  const cb = msg =>{      return ()=>{          props.callback(msg)      }  }  return (      <button onClick={cb("你好!")}>你好</button>  )}// 父组件 Parentclass Parent extends Component {    callback(msg){        console.log(msg)    }    render(){        return <Child callback={this.callback.bind(this)}></Child>        }}
复制代码

如何配置 React-Router 实现路由切换

(1)使用<Route> 组件


路由匹配是通过比较 <Route> 的 path 属性和当前地址的 pathname 来实现的。当一个 <Route> 匹配成功时,它将渲染其内容,当它不匹配时就会渲染 null。没有路径的 <Route> 将始终被匹配。


// when location = { pathname: '/about' }<Route path='/about' component={About}/> // renders <About/><Route path='/contact' component={Contact}/> // renders null<Route component={Always}/> // renders <Always/>
复制代码


(2)结合使用 <Switch> 组件和 <Route> 组件


<Switch> 用于将 <Route> 分组。


<Switch>    <Route exact path="/" component={Home} />    <Route path="/about" component={About} />    <Route path="/contact" component={Contact} /></Switch>
复制代码


<Switch> 不是分组 <Route> 所必须的,但他通常很有用。 一个 <Switch> 会遍历其所有的子 <Route>元素,并仅渲染与当前地址匹配的第一个元素。


(3)使用 <Link>、 <NavLink>、<Redirect> 组件


<Link> 组件来在你的应用程序中创建链接。无论你在何处渲染一个<Link> ,都会在应用程序的 HTML 中渲染锚(<a>)。


<Link to="/">Home</Link>   // <a href='/'>Home</a>
复制代码


是一种特殊类型的 当它的 to 属性与当前地址匹配时,可以将其定义为"活跃的"。


// location = { pathname: '/react' }<NavLink to="/react" activeClassName="hurray">    React</NavLink>// <a href='/react' className='hurray'>React</a>
复制代码


当我们想强制导航时,可以渲染一个<Redirect>,当一个<Redirect>渲染时,它将使用它的 to 属性进行定向。

如何创建 refs

Refs 是使用 React.createRef() 创建的,并通过 ref 属性附加到 React 元素。在构造组件时,通常将 Refs 分配给实例属性,以便可以在整个组件中引用它们。


class MyComponent extends React.Component {  constructor(props) {    super(props);    this.myRef = React.createRef();  }  render() {    return <div ref={this.myRef} />;  }}
复制代码


或者这样用:


class UserForm extends Component {  handleSubmit = () => {    console.log("Input Value is: ", this.input.value);  };  render() {    return (      <form onSubmit={this.handleSubmit}>        <input type="text" ref={(input) => (this.input = input)} /> // Access DOM input in handle submit        <button type="submit">Submit</button>      </form>    );  }}
复制代码

为什么 useState 要使用数组而不是对象

useState 的用法:


const [count, setCount] = useState(0)
复制代码


可以看到 useState 返回的是一个数组,那么为什么是返回数组而不是返回对象呢?


这里用到了解构赋值,所以先来看一下 ES6 的解构赋值:

数组的解构赋值
const foo = [1, 2, 3];const [one, two, three] = foo;console.log(one);    // 1console.log(two);    // 2console.log(three);    // 3
复制代码
对象的解构赋值
const user = {  id: 888,  name: "xiaoxin"};const { id, name } = user;console.log(id);    // 888console.log(name);    // "xiaoxin"
复制代码


看完这两个例子,答案应该就出来了:


  • 如果 useState 返回的是数组,那么使用者可以对数组中的元素命名,代码看起来也比较干净

  • 如果 useState 返回的是对象,在解构对象的时候必须要和 useState 内部实现返回的对象同名,想要使用多次的话,必须得设置别名才能使用返回值


下面来看看如果 useState 返回对象的情况:


// 第一次使用const { state, setState } = useState(false);// 第二次使用const { state: counter, setState: setCounter } = useState(0) 
复制代码


这里可以看到,返回对象的使用方式还是挺麻烦的,更何况实际项目中会使用的更频繁。 总结:useState 返回的是 array 而不是 object 的原因就是为了降低使用的复杂度,返回数组的话可以直接根据顺序解构,而返回对象的话要想使用多次就需要定义别名了。

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, [source])接收两个参数,调用方式如下


useEffect(() => {   console.log('mounted');
return () => { console.log('willUnmount'); } }, [source]);
复制代码


生命周期函数的调用主要是通过第二个参数[source]来进行控制,有如下几种情况:


  • [source]参数不传时,则每次都会优先调用上次保存的函数中返回的那个函数,然后再调用外部那个函数;

  • [source]参数传[]时,则外部的函数只会在初始化时调用一次,返回的那个函数也只会最终在组件卸载时调用一次;

  • [source]参数有值时,则只会监听到数组中的值发生变化后才优先调用返回的那个函数,再调用外部的函数。


用户头像

xiaofeng

关注

努力写代码中 2022-08-18 加入

努力写代码中

评论

发布
暂无评论
腾讯前端必会react面试题合集_前端_xiaofeng_InfoQ写作社区