乐趣区

关于react.js:前端几个常见考察点整理

我当初有一个 button,要用 react 在下面绑定点击事件,要怎么做?

class Demo {render() {return <button onClick={(e) => {alert('我点击了按钮')
    }}>
      按钮
    </button>
  }
}

你感觉你这样设置点击事件会有什么问题吗?

因为 onClick 应用的是匿名函数,所有每次重渲染的时候,会把该 onClick 当做一个新的 prop 来解决,会将外部缓存的 onClick 事件进行从新赋值,所以绝对间接应用函数来说,可能有一点的性能降落

批改

class Demo {onClick = (e) => {alert('我点击了按钮')
  }

  render() {return <button onClick={this.onClick}>
      按钮
    </button>
  }

何为纯函数(pure function)

一个纯函数是一个不依赖于且不扭转其作用域之外的变量状态的函数,这也意味着一个纯函数对于同样的参数总是返回同样的后果。

React-Router 4 怎么在路由变动时从新渲染同一个组件?

当路由变动时,即组件的 props 产生了变动,会调用 componentWillReceiveProps 等生命周期钩子。那须要做的只是:当路由扭转时,依据路由,也去申请数据:

class NewsList extends Component {componentDidMount () {this.fetchData(this.props.location);
  }

  fetchData(location) {const type = location.pathname.replace('/', '') ||'top'
    this.props.dispatch(fetchListData(type))
  }
  componentWillReceiveProps(nextProps) {if (nextProps.location.pathname != this.props.location.pathname) {this.fetchData(nextProps.location);
     } 
  }
  render () {...}
}

利用生命周期 componentWillReceiveProps,进行从新 render 的预处理操作。

何为 action

Actions 是一个纯 javascript 对象,它们必须有一个 type 属性表明正在执行的 action 的类型。本质上,action 是将数据从应用程序发送到 store 的有效载荷。

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 必须应用 JSX 吗?

React 并不强制要求应用 JSX。当不想在构建环境中配置无关 JSX 编译时,不在 React 中应用 JSX 会更加不便。

每个 JSX 元素只是调用 React.createElement(component, props, ...children) 的语法糖。因而,应用 JSX 能够实现的任何事件都能够通过纯 JavaScript 实现。

例如,用 JSX 编写的代码:

class Hello extends React.Component {render() {return <div>Hello {this.props.toWhat}</div>;
  }
}
ReactDOM.render(
  <Hello toWhat="World" />,
  document.getElementById('root')
);

能够编写为不应用 JSX 的代码:

class Hello extends React.Component {render() {return React.createElement('div', null, `Hello ${this.props.toWhat}`);
  }
}
ReactDOM.render(React.createElement(Hello, {toWhat: 'World'}, null),
  document.getElementById('root')
);

react 中的 Portal 是什么?

Portals 提供了一种很好的将子节点渲染到父组件以外的 DOM 节点的形式。
第一个参数(child)是任何可渲染的 React 子元素,例如一个元素,字符串或碎片。
第二个参数(container)则是一个 DOM 元素。

ReactDOM.createPortal(child, container)

React 中的高阶组件使用了什么设计模式?

应用了装璜模式,高阶组件的使用:

function withWindowWidth(BaseComponent) {
  class DerivedClass extends React.Component {
    state = {windowWidth: window.innerWidth,}
    onResize = () => {
      this.setState({windowWidth: window.innerWidth,})
    }
    componentDidMount() {window.addEventListener('resize', this.onResize)
    }
    componentWillUnmount() {window.removeEventListener('resize', this.onResize);
    }
    render() {return <BaseComponent {...this.props} {...this.state}/>
    }
  }
  return DerivedClass;
}
const MyComponent = (props) => {return <div>Window width is: {props.windowWidth}</div>
};
export default withWindowWidth(MyComponent);

装璜模式的特点是不须要扭转 被装璜对象 自身,而只是在里面套一个外壳接口。JavaScript 目前曾经有了原生装璜器的提案,其用法如下:

@testable
   class MyTestableClass {}

非嵌套关系组件的通信形式?

即没有任何蕴含关系的组件,包含兄弟组件以及不在同一个父级中的非兄弟组件。

  • 能够应用自定义事件通信(公布订阅模式)
  • 能够通过 redux 等进行全局状态治理
  • 如果是兄弟组件通信,能够找到这两个兄弟节点独特的父节点, 联合父子间通信形式进行通信。

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

除了在构造函数中绑定 this,还有其它形式吗

你能够应用属性初始值设定项 (property initializers) 来正确绑定回调,create-react-app 也是默认反对的。在回调中你能够应用箭头函数,但问题是每次组件渲染时都会创立一个新的回调。

React 中 keys 的作用是什么?

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

在 React 中渲染汇合时,向每个反复的元素增加关键字对于帮忙 React 跟踪元素与数据之间的关联十分重要。key 应该是惟一 ID,最好是 UUID 或收集项中的其余惟一字符串:

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

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

Redux 状态管理器和变量挂载到 window 中有什么区别

两者都是存储数据以供前期应用。然而 Redux 状态更改可回溯——Time travel,数据多了的时候能够很清晰的晓得改变在哪里产生,残缺的提供了一套状态管理模式。

随着 JavaScript 单页利用开发日趋简单,JavaScript 须要治理比任何时候都要多的 state(状态)。这些 state 可能包含服务器响应、缓存数据、本地生成尚未长久化到服务器的数据,也包含 UI 状态,如激活的路由,被选中的标签,是否显示加载动效或者分页器等等。

治理一直变动的 state 十分艰难。如果一个 model 的变动会引起另一个 model 变动,那么当 view 变动时,就可能引起对应 model 以及另一个 model 的变动,顺次地,可能会引起另一个 view 的变动。直至你搞不清楚到底产生了什么。state 在什么时候,因为什么起因,如何变动未然不受管制。当零碎变得盘根错节的时候,想重现问题或者增加新性能就会变得举步维艰。
如果这还不够蹩脚,思考一些来自前端开发畛域的新需要,如更新调优、服务端渲染、路由跳转前申请数据等等。前端开发者正在禁受前所未有的复杂性,难道就这么放弃了吗? 当然不是。

这里的复杂性很大水平上来自于:咱们总是将两个难以理清的概念混同在一起:变动和异步。能够称它们为曼妥思和可乐。如果把二者离开,能做的很好,但混到一起,就变得一团糟。一些库如 React 视图在视图层禁止异步和间接操作 DOM 来解决这个问题。美中不足的是,React 仍旧把解决 state 中数据的问题留给了你。Redux 就是为了帮你解决这个问题。

为什么调用 setState 而不是间接扭转 state?

解答

如果您尝试间接扭转组件的状态,React 将无奈得悉它须要从新渲染组件。通过应用 setState() 办法,React 能够更新组件的 UI。

另外,您还能够谈谈如何不保障状态更新是同步的。如果须要基于另一个状态(或属性)更新组件的状态,请向 setState() 传递一个函数,该函数将 state 和 props 作为其两个参数:

this.setState((state, props) => ({counter: state.counter + props.increment}));

react-router 里的 Link 标签和 a 标签的区别

从最终渲染的 DOM 来看,这两者都是链接,都是 标签,区别是∶ <Link>是 react-router 里实现路由跳转的链接,个别配合 <Route> 应用,react-router 接管了其默认的链接跳转行为,区别于传统的页面跳转,<Link> 的“跳转”行为只会触发相匹配的<Route> 对应的页面内容更新,而不会刷新整个页面。

<Link>做了 3 件事件:

  • 有 onclick 那就执行 onclick
  • click 的时候阻止 a 标签默认事件
  • 依据跳转 href(即是 to),用 history (web 前端路由两种形式之一,history & hash)跳转,此时只是链接变了,并没有刷新页面而 <a> 标签就是一般的超链接了,用于从以后页面跳转到 href 指向的另一 个页面(非锚点状况)。

a 标签默认事件禁掉之后做了什么才实现了跳转?

let domArr = document.getElementsByTagName('a')
[...domArr].forEach(item=>{item.addEventListener('click',function () {location.href = this.href})
})

React 16 中新生命周期有哪些

对于 React16 开始利用的新生命周期:能够看出,React16 自上而下地对生命周期做了另一种维度的解读:

  • Render 阶段:用于计算一些必要的状态信息。这个阶段可能会被 React 暂停,这一点和 React16 引入的 Fiber 架构(咱们前面会重点解说)是无关的;
  • Pre-commit 阶段:所谓“commit”,这里指的是“更新真正的 DOM 节点”这个动作。所谓 Pre-commit,就是说我在这个阶段其实还并没有去更新实在的 DOM,不过 DOM 信息曾经是能够读取的了;
  • Commit 阶段:在这一步,React 会实现实在 DOM 的更新工作。Commit 阶段,咱们能够拿到实在 DOM(包含 refs)。

与此同时,新的生命周期在流程方面,依然遵循“挂载”、“更新”、“卸载”这三个狭义的划分形式。它们别离对应到:

  • 挂载过程:

    • constructor
    • getDerivedStateFromProps
    • render
    • componentDidMount
  • 更新过程:

    • getDerivedStateFromProps
    • shouldComponentUpdate
    • render
    • getSnapshotBeforeUpdate
    • componentDidUpdate
  • 卸载过程:

    • componentWillUnmount

**

React 与 Vue 的 diff 算法有何不同?

diff 算法是指生成更新补丁的形式,次要利用于虚构 DOM 树变动后,更新实在 DOM。所以 diff 算法肯定存在这样一个过程:触发更新 → 生成补丁 → 利用补丁。

React 的 diff 算法,触发更新的机会次要在 state 变动与 hooks 调用之后。此时触发虚构 DOM 树变更遍历,采纳了深度优先遍历算法。但传统的遍历形式,效率较低。为了优化效率,应用了分治的形式。将繁多节点比对转化为了 3 种类型节点的比对,别离是树、组件及元素,以此晋升效率。

  • 树比对:因为网页视图中较少有跨层级节点挪动,两株虚构 DOM 树只对同一档次的节点进行比拟。
  • 组件比对:如果组件是同一类型,则进行树比对,如果不是,则间接放入到补丁中。
  • 元素比对:次要产生在同层级中,通过标记节点操作生成补丁,节点操作对应实在的 DOM 剪裁操作。

以上是经典的 React diff 算法内容。自 React 16 起,引入了 Fiber 架构。为了使整个更新过程可随时暂停复原,节点与树别离采纳了 FiberNode 与 FiberTree 进行重构。fiberNode 应用了双链表的构造,能够间接找到兄弟节点与子节点。整个更新过程由 current 与 workInProgress 两株树双缓冲实现。workInProgress 更新实现后,再通过批改 current 相干指针指向新节点。

Vue 的整体 diff 策略与 React 对齐,尽管不足工夫切片能力,但这并不意味着 Vue 的性能更差,因为在 Vue 3 初期引入过,前期因为收益不高移除掉了。除了高帧率动画,在 Vue 中其余的场景简直都能够应用防抖和节流去进步响应性能。

React diff 算法的原理是什么?

实际上,diff 算法探讨的就是虚构 DOM 树发生变化后,生成 DOM 树更新补丁的形式。它通过比照新旧两株虚构 DOM 树的变更差别,将更新补丁作用于实在 DOM,以最小老本实现视图更新。具体的流程如下:

  • 实在的 DOM 首先会映射为虚构 DOM;
  • 当虚构 DOM 发生变化后,就会依据差距计算生成 patch,这个 patch 是一个结构化的数据,内容蕴含了减少、更新、移除等;
  • 依据 patch 去更新实在的 DOM,反馈到用户的界面上。

    一个简略的例子:

import React from 'react'
export default class ExampleComponent extends React.Component {render() {if(this.props.isVisible) {return <div className="visible">visbile</div>;}
     return <div className="hidden">hidden</div>;
  }
}

这里,首先假设 ExampleComponent 可见,而后再扭转它的状态,让它不可见。映射为实在的 DOM 操作是这样的,React 会创立一个 div 节点。

<div class="visible">visbile</div>

当把 visbile 的值变为 false 时,就会替换 class 属性为 hidden,并重写外部的 innerText 为 hidden。这样一个生成补丁、更新差别的过程统称为 diff 算法。

diff 算法能够总结为三个策略,别离从树、组件及元素三个层面进行复杂度的优化:

策略一:疏忽节点跨层级操作场景,晋升比对效率。(基于树进行比照)

这一策略须要进行树比对,即对树进行分层比拟。树比对的解决手法是十分“暴力”的,即两棵树只对同一档次的节点进行比拟,如果发现节点曾经不存在了,则该节点及其子节点会被齐全删除掉,不会用于进一步的比拟,这就晋升了比对效率。

策略二:如果组件的 class 统一,则默认为类似的树结构,否则默认为不同的树结构。(基于组件进行比照)

在组件比对的过程中:

  • 如果组件是同一类型则进行树比对;
  • 如果不是则间接放入补丁中。

只有父组件类型不同,就会被从新渲染。这也就是为什么 shouldComponentUpdate、PureComponent 及 React.memo 能够进步性能的起因。

策略三:同一层级的子节点,能够通过标记 key 的形式进行列表比照。(基于节点进行比照)

元素比对次要产生在同层级中,通过标记节点操作生成补丁。节点操作蕴含了插入、挪动、删除等。其中节点从新排序同时波及插入、挪动、删除三个操作,所以效率耗费最大,此时策略三起到了至关重要的作用。通过标记 key 的形式,React 能够间接挪动 DOM 节点,升高内耗。

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

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

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

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

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

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

依据上面定义的代码,能够找出存在的两个问题吗?

请看上面的代码:
[外链图片转存失败, 源站可能有防盗链机制, 倡议将图片保留下来间接上传 (img-3ccno3kI-1665626283201)(https://segmentfault.com/img/… “ 图片形容 ”)]
答案:
1. 在构造函数没有将 props 传递给 super,它应该包含以下行

constructor(props) {super(props);
// ...
}

2. 事件监听器 (通过addEventListener() 调配时)的作用域不正确,因为 ES6 不提供主动绑定。因而,开发人员能够在构造函数中重新分配 clickHandler 来蕴含正确的绑定:

constructor(props) {super(props);
this.clickHandler = this.clickHandler.bind(this);
// ...
}

为什么 JSX 中的组件名要以大写字母结尾

因为 React 要晓得以后渲染的是组件还是 HTML 元素

退出移动版