react高频面试题

1.说说React生命周期中有哪些坑?如何避免?

React生命周期
生命周期概览
生命周期的状态
组件的生命周期可分成三个状态:

Mounting:已插入真实 DOM
Updating:正在被重新渲
Unmounting:已移出真实 DOM
componentWillMount 在渲染前调用,在客户端也在服务端。
生命周期介绍
componentDidMount :

在第一次渲染后调用,只在客户端。之后组件已经生成了对应的DOM结构,可以通过this.getDOMNode()来进行访问。 如果你想和其他JavaScript框架一起使用,可以在这个方法中调用setTimeout, setInterval或者发送AJAX请求等操作(防止异步操作阻塞UI)。

componentWillReceiveProps

在组件接收到一个新的 prop (更新后)时被调用。这个方法在初始化render时不会被调用。

shouldComponentUpdate

返回一个布尔值。在组件接收到新的props或者state时被调用。在初始化时或者使用forceUpdate时不被调用。
可以在你确认不需要更新组件时使用。

componentWillUpdate

在组件接收到新的props或者state但还没有render时被调用。在初始化时不会被调用。

componentDidUpdate

在组件完成更新后立即调用。在初始化时不会被调用。

componentWillUnmount

在组件从 DOM 中移除之前立刻被调用。

React16.3生命周期
安装
在创建组件的实例并将其插入DOM时,将按以下顺序调用这些方法:

constructor()

React组件的构造函数在安装之前被调用。在实现React.Component子类的构造函数时,应该super(props)在任何其他语句之前调用。否则,this.props将在构造函数中未定义,这可能导致错误。

通常,在React中,构造函数仅用于两个目的:

通过分配对象来初始化本地状态this.state。
将事件处理程序方法绑定到实例。
不应该打电话setState()给constructor()。相反,如果您的组件需要使用本地状态,请直接在构造函数中指定初始状态this.state。

构造函数是his.state直接分配的唯一位置。在所有其他方法中,需要使用this.setState()。

static getDerivedStateFromProps()

getDerivedStateFromProps在调用render方法之前调用,无论是在初始安装还是后续更新。它应该返回一个更新状态的对象,或者返回null以不更新任何状态。

render()

render()方法是类组件中唯一必需的方法。

调用时,它应检查this.props并this.state返回以下类型之一:

React elements。通常通过JSX创建。
Arrays and fragments。让您从渲染中返回多个元素。有关更多详细信息,请参阅片段文档。
Portals。
字符串和数字。它们在DOM中呈现为文本节点。
布尔或null。什么都没有。
该render()函数应该无状态的,这意味着它不会修改组件状态,每次调用时都返回相同的结果,并且它不直接与浏览器交互。

如果您需要与浏览器进行交互,请执行componentDidMount()或其他生命周期方法。保持render()纯粹使组件更容易思考。

如果shouldComponentUpdate()返回false,则render()不会被调用

componentDidMount()

componentDidMount()在安装组件(插入树中)后立即调用。需要DOM节点的初始化应该放在这里。如果需要从远程端点加载数据,这是实例化网络请求的好地方。
此方法是设置任何订阅的好地方。如果您这样做,请不要忘记取消订阅componentWillUnmount()。
您可以在componentDidMount()立即使用this.setState()。它将触发额外的渲染,但它将在浏览器更新屏幕之前发生。这保证即使render()在这种情况下将被调用两次,用户也不会看到中间状态。请谨慎使用此模式,因为它通常会导致性能问题。在大多数情况下,您应该能够分配初始状态constructor()。但是,当您需要在渲染依赖于其大小或位置的东西之前测量DOM节点时,可能需要对模态和工具提示等情况进行处理。
这些方法被认为是遗留的,应该在新代码中避免它们:

UNSAFE_componentWillMount()

更新
props or state 的更改可能导致更新。重新渲染组件时,将按以下顺序调用这些方法:

static getDerivedStateFromProps()

render()

getSnapshotBeforeUpdate()

getSnapshotBeforeUpdate(prevProps, prevState)

getSnapshotBeforeUpdate()在最近呈现的输出被提交到例如DOM之前调用。它使得组件可以在可能更改之前从DOM捕获一些信息(例如滚动位置)。此生命周期返回的任何值都将作为参数传递给componentDidUpdate()。

此用例并不常见,但它可能出现在需要以特殊方式处理滚动位置的聊天线程等UI中。

官网的例子

componentDidUpdate()
componentDidUpdate()更新发生后立即调用。初始渲染不会调用此方法。

将此作为在更新组件时对DOM进行操作的机会。只要您将当前道具与之前的道具进行比较(例如,如果道具未更改,则可能不需要网络请求),这也是进行网络请求的好地方。

componentDidUpdate()但要注意,必须在一个条件下被包裹就像上面的例子中,否则会导致无限循环。它还会导致额外的重新渲染,虽然用户不可见,但会影响组件性能。

componentDidUpdate():如果shouldComponentUpdate()返回false,则不会被调用。

这些方法被认为是遗留的,您应该在新代码中避免它们:

UNSAFE_componentWillUpdate()

UNSAFE_componentWillReceiveProps()

卸载
从DOM中删除组件时调用此方法:

componentWillUnmount()

componentWillUnmount()在卸载和销毁组件之前立即调用。在此方法中执行任何必要的清理,例如使计时器无效,取消网络请求或清除在其中创建的任何订阅componentDidMount()。

不应该调用setState(),componentWillUnmount()因为组件永远不会被重新呈现。卸载组件实例后,将永远不会再次安装它。

错误处理
在渲染期间,生命周期方法或任何子组件的构造函数中发生错误时,将调用这些方法。

static getDerivedStateFromError()

static getDerivedStateFromError(error)

在后代组件抛出错误后调用此生命周期。它接收作为参数抛出的错误,并应返回值以更新状态。

componentDidCatch()

componentDidCatch(error, info)

在后代组件抛出错误后调用此生命周期。它接收两个参数:

error - 抛出的错误。
info- componentStack包含键的对象,其中包含有关哪个组件引发错误的信息。

如果发生错误,可以componentDidCatch()通过调用呈现回退UI setState,但在将来的版本中将不推荐使用。使用static getDerivedStateFromError()处理回退,而不是渲染。

componentDidCatch()在“提交”阶段被调用,因此允许副作用。它应该用于记录错误之类的事情

class ErrorBoundary extends React.Component {
constructor(props) {
super(props);

this.state = { hasError: false };

}

static getDerivedStateFromError(error) {
// Update state so the next render will show the fallback UI.

return { hasError: true };

}

componentDidCatch(error, info) {
// Example “componentStack”:

// in ComponentThatThrows (created by App)

// in ErrorBoundary (created by App)

// in div (created by App)

// in App

logComponentStackToMyService(info.componentStack);

}

render() {
if (this.state.hasError) {
// You can render any custom fallback UI

return

Something went wrong.
;
}

return this.props.children;

}

}

2.说说Real diff算法是怎么运作的?


React 的 Reconciliation 算法原理
React 的渲染机制 Reconciliation 过程

React 采用的是虚拟 DOM (即 VDOM ),每次属性 (props) 和状态 (state) 发生变化的时候,render 函数返回不同的元素树,React 会检测当前返回的元素树和上次渲染的元素树之前的差异,然后针对差异的地方进行更新操作,最后渲染为真实 DOM,这就是整个 Reconciliation 过程,其核心就是进行新旧 DOM 树对比的 diff 算法。

diff 算法
在某一时间节点调用 React 的 render() 方法,会创建一棵由 React 元素组成的树。在下一次 state 或 props 更新时,相同的 render() 方法会返回一棵不同的树。React 需要基于这两棵树之间的差别来判断如何有效率的更新 UI 以保证当前 UI 与最新的树保持同步。

在上图第三部分,新旧DOM树比对所用的算法即 Diff算法

React diff策略

Web UI 中 DOM 节点跨层级的移动操作特别少,可以忽略不计。【永远只比较同层节点,不会跨层级比较节点。】
拥有相同类的两个组件将会生成相似的树形结构,拥有不同类的两个组件将会生成不同的树形结构。
对于同一层级的一组子节点,它们可以通过唯一 key 进行区分。
基于以上三个前提策略,React 分别对 tree diff、component diff 以及 element diff 进行算法优化,事实也证明这三个前提策略是合理且准确的,它保证了整体界面构建的性能。

执行流程(规则)
1、元素类型不相同时

直接将 原 VDOM 树上该节点以及该节点下所有的后代节点 全部删除,然后替换为新 VDOM 树上同一位置的节点

当根节点为不同类型的元素时,React 会拆卸原有的树并且建立起新的树。当拆卸一棵树时,对应的 DOM 节点也会被销毁。组件实例将执行 componentWillUnmount() 方法。当建立一棵新的树时,对应的 DOM 节点会被创建以及插入到 DOM 中。组件实例将执行 componentWillMount() 方法,紧接着 componentDidMount() 方法。所有跟之前的树所关联的 state 也会被销毁。

元素类型相同时
a. 都是 DOM 节点

React 会保留 DOM 节点,仅比对及更新有改变的属性。

通过比对这两个元素,React 知道需要修改 DOM 元素上的 className 属性和 title 属性。
处理完该节点后,React 继续对子节点进行递归。

b. 都是组件元素

对于同一类型的组件,根据Virtual DOM是否变化也分两种,可以用shouldComponentUpdate()判断Virtual DOM是否发生了变化,若没有变化就不需要再进行diff,这样可以节省大量时间,若变化了,就按原策略进行比较
对于非同一类的组件,则将该组件判断为 dirty component,从而替换整个组件下的所有子节点。
当一个组件更新时,组件实例保持不变,这样 state 在跨越不同的渲染时保持一致。React 将更新该组件实例的 props 以跟最新的元素保持一致,并且调用该实例的 componentWillReceiveProps() 和 componentWillUpdate() 方法。

3.调和阶段setState干了什么?


1)代码中调用 setState 函数之后,React 会将传入的参数对象与组件当前的状态合并,然后触发所谓的调和过程(Reconciliation)。
2)经过调和过程,React 会以相对高效的方式根据新的状态构建 React 元素树并且着手重新渲染整个 UI 界面;
3)在 React 得到元素树之后,React 会自动计算出新的树与老树的节点差异,然后根据差异对界面进行最小化重渲染;
4)在差异计算算法中,React 能够相对精确地知道哪些位置发生了改变以及应该如何改变,这就保证了按需更新,而不是全部重新渲染。

setState是异步还是同步:

合成事件中是异步
钩子函数中的是异步
原生事件中是同步
setTimeout中是同步

4.说说redux的实现原理是什么,写出核心代码?


前言
相信很多人都在使用redux作为前端状态管理库进去项目开发,但仍然停留在“知道怎么用,但仍然不知道其核心原理”的阶段,接下来带大家分析一下redux和react-redux两个库的核心思想和API

redux
1.为什么要使用redux?
随着互联网的高速发展,我们的应用变得越来越复杂,进行导致我们的组件之间的关系也变得日趋复杂,往往需要将状态父组件 -> 子组件 -> 子子组件 -> 又或者只是简单的 父组件与子组件之间的props传递也会导致我们的数据流变得难以维护,因为二次开发者不在熟悉项目的情况下无法第一时间确定数据来源是由谁发起的。使用redux之后,所有的状态都来自于store中的state,并且store通过react-redux中的Provider组件可以传递到Provider组件下的所有组件,也就是说store中的state对于Provider下的组件来说就是全局的。

2.redux的核心原理是什么?
1.将应用的状态统一放到state中,由store来管理state。
2.reducer的作用是返回一个新的state去更新store中对用的state。
3.按redux的原则,UI层每一次状态的改变都应通过action去触发,action传入对应的reducer 中,reducer返回一个新的state更新store中存放的state,这样就完成了一次状态的更新
4.subscribe是为store订阅监听函数,这些订阅后的监听函数是在每一次dipatch发起后依次执行
5.可以添加中间件对提交的dispatch进行重写

3.redux的api有哪些?
1.createStore 创建仓库,接受reducer作为参数
2.bindActionCreator 绑定store.dispatch和action 的关系
3.combineReducers 合并多个reducers
4.applyMiddleware 洋葱模型的中间件,介于dispatch和action之间,重写dispatch
5.compose 整合多个中间件

接下来我们来依次实现createStore、bindActionCreator、combineReducers、applyMiddleware、compose

createStore的实现

注意: createStore并没有直接返回store中存放的state,而是返回一个函数getState来获取state,当我们调用getState去获取state时,需要返回一个state的复制品,也就是需要返回一个深拷贝state之后对象,这样可以避免state值的非法篡改,因为如何直接返回state的话,只需通过state[key] = xxxx就能对state进行修改,违背了redux只能通过dispatch(action)去更新state

bindActionCreator的实现

bindActionCreator是为action函数外面包一层dispatch,这样在进行action发起时无需再手动dispatch了

combineReducers的实现

applyMiddleware的实现

compose的实现

compose是整合多个中间件的情况,这里使用reduce对用传入的中间件进行累加执行

react-redux
1.为什么要使用react-redux?
回答这个问题,只需要明确我们的最终目的:能在react组件中实现对store中的state进行获取、修改、监听,而从上述内容可以知道,createStore会给我们返回getState、dispatch、subscribe来进行获取state、修改state、监听state变化,而我们现在要做的就是把这个三个函数传递给react组件就可以了,所以我们就需要react-redux来帮助我们

2.react-redux的核心原理是什么?
1.将Provider高阶组件包裹在组件的最外层,并且将创建的store传入Provider高阶组件中,
然后再Provider高阶组件内部获取传入的store并将它添加到Provider高阶组件的context上下文中,context是react组件特有的一种不用手动一层层传递props就能在组件树中传递数据的方式,这样就实现了store相对于react组件的全局化,所有组件都能对store进行修改,获取,监听了

2.虽然Provider下的组件都拥有可以操作store的能力了,但是由于倘若我们要在每一个组件都从context中去获取store会造成大量代码冗余,还有一点就是即使你能在react组件中能够操作store了,但是当你dispatch一个action之后,store中的state虽然更新了,但是并不会触发组件中的render函数去渲染新的数据,所以我们就需要通过react-redux另一个高阶组件connect
来做集中处理。connect组件接受一个component组件返回一个新的component组件,在connect最终返回的组件中获取store并将store设置为当前组件的state,并且在connect返回的组件中的componentDidMount周期函数中调用subscribe给store绑定监听函数,而这个监听函数就是负责当前store中的state发生改变时,通过this.setState来触发组件的render函数的调用,最终达到store中的state与UI中state同步的问题

3.react-redux有哪些API?
1.Provider组件通过context的方式将store注入应用
2.Connect高阶组件,获取并监听store,然后根据store state和组件自身props计算得到新props,注入该组件,并且可以通过监听store,比较计算出的新props判断是否需要更新组件

Provider的实现

若存在疑问请回顾上面react-redux的原理分析

5.React合成事件的原理?


1、React合成事件是什么?
React 合成事件(SyntheticEvent)是 React 模拟原生 DOM 事件所有能力的一个事件对象,即浏览器原生事件的跨浏览器包装器。它根据 W3C 规范 来定义合成事件,兼容所有浏览器,拥有与浏览器原生事件相同的接口。

2、为什么会有合成事件?
将事件绑定在document - v16/容器元素 - v17统一管理,防止很多事件直接绑定在原生的dom元素上。造成一些不可控的情况
React 想实现一个全浏览器的框架, 为了实现这种目标就需要提供全浏览器一致性的事件系统,以此抹平不同浏览器的差异。
3、深入合成事件
3.1、事件工作流回顾
3.1.1、事件流
DOM2 Events 规范规定事件流分为 3 个阶段:事件捕获、到达目标和事件冒泡。事件捕获最先发生,为提前拦截事件提供了可能。然后,实际的目标元素接收到事件。最后一个阶段是冒泡,最迟要在这个阶段响应事件。以前面那个简单的 HTML 为例,点击

元素会以如图 17-3 所示的顺序触发事件。

6.React组件之间如何通信?


组件间通信⽅式一共有如下几种:

1.⽗组件向⼦组件通讯
⽗组件可以通过向⼦组件传 props 的⽅式来实现父到子的通讯。

2.⼦组件向⽗组件通讯
可以采用 props + 回调 的⽅式。

当⽗组件向⼦组件传递 props 进⾏通讯时,可在该 props 中传递一个回调函数,当⼦组件调⽤该函数时,可将⼦组件中想要传递给父组件的信息作为参数传递给该函数。由于 props 中的函数作⽤域为⽗组件⾃身,因此可以通过该函数内的 setState 更新到⽗组件上。

3.兄弟组件通信
可以通过兄弟节点的共同⽗节点,再结合以上2种⽅式,由⽗节点转发信息,实现兄弟间通信。

4.跨层级通信
可以采用 React 中的 Context 来实现跨越多层的全局数据通信。

Context 设计的⽬的是为在⼀个组件树中共享 “全局” 数据,如:当前已登录的⽤户、界面主题、界面语⾔等信息。

5.发布订阅模式
发布者发布事件,订阅者监听到事件后做出反应。

我们可以通过引⼊ event 模块进⾏此种方式的通信。

6.全局状态管理⼯具
可以借助 Redux 或 Mobx 等全局状态管理⼯具进⾏通信,它们会维护⼀个全局状态中⼼(Store),并可以根据不同的事件产⽣新的状态。

7.为什么react元素有一个$$type属性?


你可能认为你在写JSX:

hi

但是实际上是你在调用一个函数:

React.createElement(
/* type / ‘marquee’,
/ props / { bgcolor: ‘#ffa7c4’ },
/ children */ ‘hi’
)

这个函数给你返回了一个对象,我们把这个对象叫做React元素。它告诉React接下来渲染什么,组件就是返回对象?。

{
type: ‘marquee’,
props: {
bgcolor: ‘#ffa7c4’,
children: ‘hi’,
},
key: null,
ref: null,
$$typeof: Symbol.for(‘react.element’), // ? Who dis
}

像上面这样,如果你使用React你可能熟悉type, props, key, ref这些字段。但是$$typeof是什么?为什么会有个Symbol作为值? 这个也是你在写react的时候不需要知道的一件事,但是如果你知道了,那感觉会很棒。在这篇文章中还有一些你可能想知道的安全性的提示。也许有一天你会编写自己的UI库,所有这些都会派上用场。我希望是这样的。

在客户端UI库变得普遍并添加一些基本保护之前,应用程序代码通常构造HTML并将其插入DOM:

const messageEl = document.getElementById(‘message’);
messageEl.innerHTML = ‘

’ + message.text + ‘

’;
这就可以了,除非当message.text是像 ''这样的时候。 你不希望陌生人编写的内容显示在应用程序呈现的HTML中。 (有趣的事实:如果你只做客户端渲染,这里的script标签不会让你运行JavaScript。但是,不要让这使你陷入虚假的安全感。) 为了防止此类攻击,你可以使用安全的API,例如document.createTextNode或textContent,它只处理文本。你还可以通过在用户提供的文本中替换<,>等其他潜在危险字符来抢先“转义”输入。 尽管如此,错误的成本很高,每次将用户编写的字符串插入输出时,记住它都很麻烦。这就是为什么像React这样的现代库在默认的情况下为字符串转义文本内容的原因:

{message.text}

如果message.text是带有或其他的标签,则它不会变成真正的标签(tag)。React将转义内容,然后将其插入DOM。所以你应该看标记而不是看img标签。 要在React元素中呈现任意HTML,你必须写dangerouslySetInnerHTML = {{__ html:message.text}}。然而事实上,这么笨拙的写法是一个功能。 它意味着高度可见,便于在代码审查和代码库审计中捕获它。

这是否意味着React对于注入攻击是完全安全的?不是。 HTML和DOM提供了大量的攻击面,对于React或其他UI库来说,要缓解这些攻击面要么太难要么太慢。大多数剩余的攻击都偏向于属性上进行。 例如,如果渲染 <ahref={user.website}>,请注意其user.website可能是“javascript:stealYourPassword()”。像 <div{…userData}>那样扩展用户的输入很少见,但也很危险。 React可以随着时间的推移提供更多保护,但在许多情况下,这些都是服务器问题的结果,无论如何都应该在那里修复。 仍然,转义文本内容是合理的第一道防线,可以捕获大量潜在的攻击。知道像这样的代码是安全的,这不是很好吗?

// Escaped automatically

{message.text}

好吧,这也不总是正确的。 这时候就需要派$$typeof上场了。

React的elements在设计的时候就决定是一个对象。
{
type: ‘marquee’,
props: {
bgcolor: ‘#ffa7c4’,
children: ‘hi’,
},
key: null,
ref: null,
$$typeof: Symbol.for(‘react.element’),
}

虽然通常使用React.createElement创建它们,但它不是必要的。React有一些有效的用例来支持像我刚刚上面所做的那样编写的普通元素对象。当然,你可能不希望像这样编写它们 - 但这对于优化编译器,在工作程序之间传递UI元素或者将JSX与React包解耦是有用的。 但是,如果你的服务器有一个漏洞,允许用户存储任意JSON对象, 而客户端代码需要一个字符串,这可能会成为一个问题:

// Server could have a hole that lets user store JSON
let expectedTextButGotJSON = {
type: ‘div’,
props: {
dangerouslySetInnerHTML: {
__html: ‘/* put your exploit here */’
},
},
// …
};
let message = { text: expectedTextButGotJSON };

// Dangerous in React 0.13

{message.text}

在这种情况下,React 0.13很容易受到XSS攻击。再次澄清一下,这种攻击取决于现有的服务器漏洞。 尽管如此,React可以做到更好,防止遭受它攻击。从React 0.14开始,它做到了。 React 0.14中的修复是使用Symbol标记每个React元素:

type: ‘marquee’,
props: {
bgcolor: ‘#ffa7c4’,
children: ‘hi’,
},
key: null,
ref: null,
KaTeX parse error: Expected 'EOF', got '}' at position 38: …act.element'), }̲ 这是有效的,因为你不能只把S… typeof,如果元素丢失或无效,将拒绝处理该元素。 并且使用Symbol.for的好处是符号在iframe和worker等环境之间是全局的。因此,即使在更奇特的条件下,此修复也不会阻止在应用程序的不同部分之间传递可信元素。同样,即使页面上有多个React副本,它们仍然可以继续工作。

那些不支持Symbols的浏览器呢? 好吧,他们没有得到这种额外的保护。 React仍然在元素上包含$$ typeof字段以保持一致性,但它设置为一个数字 - 0xeac7。 为什么是个具体的号码? 0xeac7看起来有点像“React”

8.说说Connect组件的原理是什么?
1、connect和Provider的使用
// App.jsx
import React from ‘react’
import { render } from ‘react-dom’
import { Provider } from ‘react-redux’
import createStore from ‘redux’
import reducer from ‘./reducers’
import Container from ‘./Container’
const store = createStore(reducer)
const App = () => {
return (

)
}
render(, document.getElementById(‘app’))
容器组件

// Container.jsx
import React from ‘react’
import { connect } from ‘react-redux’
const mapStateToProps = (state, ownProps) => ({})
const mapDispatchToProps = (dispatch, ownProps) => ({})
export default connect(mapStateToProps, mapDispatchToProps)(Demo)
2、源码解析
先看一看react-redux包的目录结构,其中es目录适用于ES模块导入,lib适用于commonjs模块导入

2.1、Provider源码解析

Provider组件在Provider.js里面定义,仅有短短几十行代码,核心代码如下:

import { ReactReduxContext } from ‘./Context’;
function Provider(_ref) {
var store = _ref.store, // 获取组件绑定的store
context = _ref.context,
children = _ref.children; // 获取子组件
// contextValue的值为{store, subscription}
var contextValue = useMemo(function () {
var subscription = new Subscription(store);
subscription.onStateChange = subscription.notifyNestedSubs;
return {
store: store,
subscription: subscription
};
}, [store]);
var previousState = useMemo(function () {
return store.getState();
}, [store]);
useEffect(function () {
var subscription = contextValue.subscription;
subscription.trySubscribe();
if (previousState !== store.getState()) {
subscription.notifyNestedSubs();
}
return function () {
subscription.tryUnsubscribe();
subscription.onStateChange = null;
};
}, [contextValue, previousState]);
// 如果Provider组件上绑定了context就是用绑定的context,如果没有绑定context,就会自己生成context
var Context = context || ReactReduxContext;
return React.createElement(Context.Provider, {
value: contextValue
}, children);
}
export default Provider;
源码中使用了useMemo钩子函数,只有在第二个参数发生变化时,第一个参数函数才会执行,可以提升代码执行性能,避免每次组件渲染都要执行函数。详情可以去查看官网,这里制作简单介绍。

var Context = context || ReactReduxContext;
return React.createElement(Context.Provider, {
value: contextValue
}, children);
我们看看这部分代码,如果Provider组件上绑定了context就是用绑定的context,如果没有绑定context,就会自己生成context。ReactReduxContext的生成在Context.js中:

import React from ‘react’;
export var ReactReduxContext =
/#PURE/
React.createContext(null);
if (process.env.NODE_ENV !== ‘production’) {
ReactReduxContext.displayName = ‘ReactRedux’;
}
export default ReactReduxContext;
有了context就可以向子组件提供store。

// 等价于
connect(mapStateToProps, mapDispatchToProps)(Demo)
可以猜想到connect(mapStateToProps, mapDispatchToProps)这部分将返回一个高阶组件,这个高阶组件的作用就是将mapStateToProps返回的state和mapDispatchToProps返回的dispatch通过props传递给Demo。我们通过源码来验证一下猜想是否正确。

connect函数在connect.js中实现,函数架子大概就是如下样子:

export function createConnect(_temp) {
// coding…
return function connect(mapStateToProps, mapDispatchToProps, mergeProps, _ref2) {
// coding…
return connectHOC();
};
}
export default createConnect();
connectHOC函数返回的就是一个高阶组件,它在connectAdvanced.js中实现,connectAdvanced这个函数就是connectHOC:

export default function connectAdvanced(selectorFactory, _ref) {
// coding…
return function wrapWithConnect(WrappedComponent) {
// coding…
function ConnectFunction(props) {
// coding…
var renderedChild = useMemo(function () {
if (shouldHandleStateChanges) {
// If this component is subscribed to store updates, we need to pass its own
// subscription instance down to our descendants. That means rendering the same
// Context instance, and putting a different value into the context.
return React.createElement(ContextToUse.Provider, {
value: overriddenContextValue
}, renderedWrappedComponent);
}
return renderedWrappedComponent;
}, [ContextToUse, renderedWrappedComponent, overriddenContextValue]);
return renderedChild;
}
var Connect = pure ? React.memo(ConnectFunction) : ConnectFunction;
Connect.WrappedComponent = WrappedComponent;
Connect.displayName = displayName;
if (forwardRef) {
var forwarded = React.forwardRef(function forwardConnectRef(props, ref) {
return React.createElement(Connect, _extends({}, props, {
forwardedRef: ref
}));
});
forwarded.displayName = displayName;
forwarded.WrappedComponent = WrappedComponent;
return hoistStatics(forwarded, WrappedComponent);
}
return hoistStatics(Connect, WrappedComponent);
};
}

8.说说你对fiber架构的理解?解决了什么问题?


前言
在 react16 没有正式公布以前,业界的人员以为这次的 react16 就叫做 Fiber,足以说明 Fiber 的重要性。fiber 在英文中意为纤维,此处意为比线程还细的单位。Facebook 取名 Fiber 的意思是为了描述一个比线程更小单位的渲染机制。用一句话来描述我所理解的 Fiber 架构:

为了解决什么样的问题
每次更新都不是无病呻吟的改变,而是为了解决问题,Fiber 的引入主要是为了解决在网页里面用户和网页应用进行交互的问题:

在 react16 之前的版本中,组建的渲染是同步的动作,如果组件包含很多层子组件,渲染时间比较长,在组件渲染的过程中又无法被打断,会导致这期间用户无法与网页进行交互。
所有的任务都是按照先后顺序,没有优先级可言,这样就会导致优先级比较高的任务无法优先被执行。
react15 存在的问题
我们用下面的一个案例来描述 react15 存在的问题: 在一个页面元素很多,且需要频繁刷新的情况下,react15 会出现失帧的情况,如下图: demo地址:claudiopro.github.io/react-fiber…

分析出现这个问题的原因:

其根本原因是:大量的同步计算任务阻塞了浏览器的 UI 渲染。默认情况下,JS 运算、页面布局和页面绘制都是运行在浏览器的主线程当中,他们之间是互斥的关系。如果 JS 运算持续占用主线程,页面就没法得到及时的更新。当我们调用 setState 更新页面的时候,React 会遍历应用的所有节点,计算出差异,然后再更新UI,整个过程是一气呵成,不能被打断的。如果页面元素很多,整个过程占用的时机就可能超过16毫秒,就容易出现掉帧的现象。 针对这一问题,React 团队从框架层面对 web 页面的运行机制做了优化,得到很好的效果。改变之后的效果见下图:

react 的解题思路:

解决主线程长时间被 JS 运算占用这一问题的基本思路,是将运算切割为多个步骤,分批完成。也就是说在完成一部分任务之后,再将控制权交回给浏览器,让浏览器有时间进行页面的渲染。等浏览器忙完之后,再继续之前未完成的任务。

react16 的新答卷
react 框架内部的运作可以分为三层:

Virtual DOM 层:描述页面长什么样
Reconciler 层:负责调用组件声明周期方法,进行 Diff 运算等。
Renderer 层:根据不同的平台,渲染出相应的页面,比较常见的是 ReactDOM 和 ReactNative。
这里改动最大的当属 Reconciler 层了,为了和之前的做区别,我们有了如下称呼的约定:

Stack Reconciler(react15 的Reconciler)
Fiber Reconciler(react16 的Reconciler)
Stack Reconciler 的运行过程

Stack Reconciler 运作的过程是不能被打断的,必须一条道走到黑:

Fiber Reconciler 的运行过程

Fiber Reconciler 每执行一段时间,都会将控制权交回给浏览器,可以分段执行:

Fiber Reconciler 是如何实现的

任务优先级

为了达到这种效果,需要有一个调度器(schedular)来根据任务的优先级进行任务的分配。优先级高的任务(如键盘输入)可以打断优先级低的任务的执行,从而更快的生效。任务的优先级分为六种:

synchronous:与之前的Stack Reconciler操作一样,同步执行
task:在 next tick 之前执行
animation:下一帧立即执行
high:在不久的将来立即执行
low:稍微延迟执行也没有关系
offscreen:下一次 render 时或者 scroll 时才执行
Fiber Reconciler 的执行过程

在react的生命周期中,以 render 为界限,分为两个阶段,分别为:

render phase: 这一阶段的生命周期是可以被打断的,每隔一段时间就会跳出当前进程,去看下是否有优先级更高的任务在等待执行
commit phase: 这一阶段的生命周期是不可以被打断的,react 会将所有的变更一次性更新到 DOM 上

了解下 render phase 的机制

render phase 这个阶段所做的事非常重要,因此我们需要了解下 render phase 的机制。

不被打断: 如果不被打断,那么在 render phase 阶段执行完会直接进入 render 函数,构建真实的 virtualDomTree
被打断: 如果组件在 render phase 过程中被打断,react 会放弃当前组件干到一半的事情,去做更高优先级的任务。当高优先级任务执行完之后,react 通过 callback 回到之前渲染到一半的组件,从头开始渲染。(看起来放弃已经渲染完的生命周期,会有点不合理,反而会增加渲染时长,但是react确实是这么干的)
这种方式的问题

因为 render phase 阶段会被打断的特性,所以 render phase 阶段的生命周期函数可能会被执行多次。因此,我们需要保证 render phase 阶段的生命周期函数是没有副作用的纯函数,确保每次执行都是一样的输出结果。

写在最后
Facebook比较慎重,16的第一个版本并没有启用这个功能,防止改变太大,影响到别的开发人员的正常工作。在 react16 晚一些的版本中会给出一个选项可以开启 fiber,在默认情况下是关闭的。

另外,facebook 在 react16 增加 Fiber 结构,其实并不是为了减少组件的渲染时间,事实上也并不会减少,反而会增加。解决的是使得一些更高优先级的任务能够优先执行,提高用户的体验,从用户的角度不会感觉到卡顿。

最后,还有一些问题并没有得到很好地解释,比如饥饿问题,即如果优先级高的任务一直存在,那么优先级低的任务将一直没有办法执行。我们期待 react 能给出优秀的解决方案。

10.说说你对redux中间件的理解?常用的中间件有哪些?实现原理?


一、Redux中间件机制
Redux本身就提供了非常强大的数据流管理功能,但这并不是它唯一的强大之处,它还提供了利用中间件来扩展自身功能,以满足用户的开发需求。首先我们来看中间件的定义:

这是Dan Abramov 对middleware的描述。简单来讲,Redux middleware 提供了一个分类处理 action 的机会。在 middleware 中,我们可以检阅每一个流过的 action,并挑选出特定类型的 action 进行相应操作,以此来改变 action。

二、理解中间价的机制
由于redux 提供了 applyMiddleware 方法来加载 middleware,因此我们首先可以看一下 redux 中关于 applyMiddleware 的源码:

export default function applyMiddleware(…middlewares) {
return createStore => (…args) => {
// 利用传入的createStore和reducer和创建一个store
const store = createStore(…args)
let dispatch = () => {
throw new Error(
)
}
const middlewareAPI = {
getState: store.getState,
dispatch: (…args) => dispatch(…args)
}
// 让每个 middleware 带着 middlewareAPI 这个参数分别执行一遍
const chain = middlewares.map(middleware => middleware(middlewareAPI))
// 接着 compose 将 chain 中的所有匿名函数,组装成一个新的函数,即新的 dispatch
dispatch = compose(…chain)(store.dispatch)
return {
…store,
dispatch
}
}
}
我们可以看到applyMiddleware的源码非常简单,但却非常精彩,具体的解读可以看我的这篇文章:
redux源码解读

从上面的代码我们不难看出,applyMiddleware 这个函数的核心就在于在于组合 compose,通过将不同的 middlewares 一层一层包裹到原生的 dispatch 之上,然后对 middleware 的设计采用柯里化的方式,以便于compose ,从而可以动态产生 next 方法以及保持 store 的一致性。

说起来可能有点绕,直接来看一个啥都不干的中间件是如何实现的:

const doNothingMidddleware = (dispatch, getState) => next => action => next(action)
上面这个函数接受一个对象作为参数,对象的参数上有两个字段 dispatch 和 getState,分别代表着 Redux Store 上的两个同名函数,但需要注意的是并不是所有的中间件都会用到这两个函数。然后 doNothingMidddleware 返回的函数接受一个 next 类型的参数,这个 next 是一个函数,如果调用了它,就代表着这个中间件完成了自己的职能,并将对 action 控制权交予下一个中间件。但需要注意的是,这个函数还不是处理 action 对象的函数,它所返回的那个以 action 为参数的函数才是。最后以 action 为参数的函数对传入的 action 对象进行处理,在这个地方可以进行操作,比如:

调动dispatch派发一个新 action 对象
调用 getState 获得当前 Redux Store 上的状态
调用 next 告诉 Redux 当前中间件工作完毕,让 Redux 调用下一个中间件
访问 action 对象 action 上的所有数据
在具有上面这些功能后,一个中间件就足够获取 Store 上的所有信息,也具有足够能力可用之数据的流转。看完上面这个最简单的中间件,下面我们来看一下 redux 中间件内,最出名的中间件 redux-thunk 的实现:

function createThunkMiddleware(extraArgument) {
return ({ dispatch, getState }) => next => action => {
if (typeof action === ‘function’) {
return action(dispatch, getState, extraArgument);
}
return next(action);
};
}
const thunk = createThunkMiddleware();
thunk.withExtraArgument = createThunkMiddleware;
export default thunk;
redux-thunk的代码很简单,它通过函数是变成的思想来设计的,它让每个函数的功能都尽可能的小,然后通过函数的嵌套组合来实现复杂的功能,我上面写的那个最简单的中间件也是如此(当然那是个瓜皮中间件)。redux-thunk 中间件的功能也很简单。首先检查参数 action 的类型,如果是函数的话,就执行这个 action 韩湖水,并把 dispatch, getState, extraArgument 作为参数传递进去,否则就调用 next 让下一个中间件继续处理 action 。

需要注意的是,每个中间件最里层处理 action 参数的函数返回值都会影响 Store 上的 dispatch 函数的返回值,但每个中间件中这个函数返回值可能都不一样。就比如上面这个 react-thunk 中间件,返回的可能是一个 action 函数,也有可能返回的是下一个中间件返回的结果。因此,dispatch 函数调用的返回结果通常是不可控的,我们最好不要依赖于 dispatch 函数的返回值。

三、redux的异步流
在多种中间件中,处理 redux 异步事件的中间件,绝对占有举足轻重的地位。从简单的 react-thunk 到 redux-promise 再到 redux-saga等等,都代表这各自解决redux异步流管理问题的方案

3.1 redux-thunk

前面我们已经对redux-thunk进行了讨论,它通过多参数的 currying 以实现对函数的惰性求值,从而将同步的 action 转为异步的 action。在理解了redux-thunk后,我们在实现数据请求时,action就可以这么写了:

function getWeather(url, params) {
return (dispatch, getState) => {
fetch(url, params)
.then(result => {
dispatch({
type: ‘GET_WEATHER_SUCCESS’, payload: result,
});
})
.catch(err => {
dispatch({
type: ‘GET_WEATHER_ERROR’, error: err,
});
});
};
}
尽管redux-thunk很简单,而且也很实用,但人总是有追求的,都追求着使用更加优雅的方法来实现redux异步流的控制,这就有了redux-promise。

3.2 redux-promise

不同的中间件都有着自己的适用场景,react-thunk 比较适合于简单的API请求的场景,而 Promise 则更适合于输入输出操作,比较fetch函数返回的结果就是一个Promise对象,下面就让我们来看下最简单的 Promise 对象是怎么实现的:

import { isFSA } from ‘flux-standard-action’;

function isPromise(val) {
return val && typeof val.then === ‘function’;
}

export default function promiseMiddleware({ dispatch }) {
return next => action => {
if (!isFSA(action)) {
return isPromise(action)
? action.then(dispatch)
: next(action);
}
 

return isPromise(action.payload)
  ? action.payload.then(
      result => dispatch({ ...action, payload: result }),
      error => {
        dispatch({ ...action, payload: error, error: true });
        return Promise.reject(error);
      }
    )
  : next(action);

};
}
它的逻辑也很简单主要是下面两部分:

先判断是不是标准的 flux action。如果不是,那么判断是否是 promise, 是的话就执行 action.then(dispatch),否则执行 next(action)。
如果是, 就先判断 payload 是否是 promise,如果是的话 payload.then 获取数据,然后把数据作为 payload 重新 dispatch({ …action, payload: result}) ;不是的话就执行 next(action)
结合 redux-promise 我们就可以利用 es7 的 async 和 await 语法,来简化异步操作了,比如这样:

const fetchData = (url, params) => fetch(url, params)
async function getWeather(url, params) {
const result = await fetchData(url, params)
if (result.error) {
return {
type: ‘GET_WEATHER_ERROR’, error: result.error,
}
}
return {
type: ‘GET_WEATHER_SUCCESS’, payload: result,
}
}
3.3 redux-saga

redux-saga是一个管理redux应用异步操作的中间件,用于代替 redux-thunk 的。它通过创建 Sagas 将所有异步操作逻辑存放在一个地方进行集中处理,以此将react中的同步操作与异步操作区分开来,以便于后期的管理与维护。对于Saga,我们可简单定义如下:

redux-saga相当于在Redux原有数据流中多了一层,通过对Action进行监听,从而捕获到监听的Action,然后可以派生一个新的任务对state进行维护(这个看项目本身的需求),通过更改的state驱动View的变更。如下图所示:

image

saga特点:

saga 的应用场景是复杂异步。
可以使用 takeEvery 打印 logger(logger大法好),便于测试。
提供 takeLatest/takeEvery/throttle 方法,可以便利的实现对事件的仅关注最近实践还是关注每一次实践的时间限频。
提供 cancel/delay 方法,可以便利的取消或延迟异步请求。
提供 race(effects),[…effects] 方法来支持竞态和并行场景。
提供 channel 机制支持外部事件。
function *getCurrCity(ip) {
const data = yield call(‘/api/getCurrCity.json’, { ip })
yield put({
type: ‘GET_CITY_SUCCESS’, payload: data,
})
}
function * getWeather(cityId) {
const data = yield call(‘/api/getWeatherInfo.json’, { cityId })
yield put({
type: ‘GET_WEATHER_SUCCESS’, payload: data,
})
}
function loadInitData(ip) {
yield getCurrCity(ip)
yield getWeather(getCityIdWithState(state))
yield put({
type: ‘GET_DATA_SUCCESS’,
})
}
总的来讲Redux Saga适用于对事件操作有细粒度需求的场景,同时它也提供了更好的可测试性,与可维护性,比较适合对异步处理要求高的大型项目,而小而简单的项目完全可以使用redux-thunk就足以满足自身需求了。毕竟react-thunk对于一个项目本身而言,毫无侵入,使用极其简单,只需引入这个中间件就行了。而react-saga则要求较高,难度较大,我现在也并没有掌握和实践这种异步流的管理方式,因此较为底层的东西先就不讨论了。

11.React性能优化的手段有哪些?


使用React.Memo来缓存组件
提升应用程序性能的一种方法是实现memoization。Memoization是一种优化技术,主要通过存储昂贵的函数调用的结果,并在再次发生相同的输入时返回缓存的结果,以此来加速程序。
父组件的每次状态更新,都会导致子组件重新渲染,即使传入子组件的状态没有变化,为了减少重复渲染,我们可以使用React.memo来缓存组件,这样只有当传入组件的状态值发生变化时才会重新渲染。如果传入相同的值,则返回缓存的组件。示例如下:

export default React.memo((props) => {
return (
{props.value}

)
});
使用useMemo缓存大量的计算
有时渲染是不可避免的,但如果您的组件是一个功能组件,重新渲染会导致每次都调用大型计算函数,这是非常消耗性能的,我们可以使用新的useMemo钩子来“记忆”这个计算函数的计算结果。这样只有传入的参数发生变化后,该计算函数才会重新调用计算新的结果。
通过这种方式,您可以使用从先前渲染计算的结果来挽救昂贵的计算耗时。总体目标是减少JavaScript在呈现组件期间必须执行的工作量,以便主线程被阻塞的时间更短。
// 避免这样做
function Component(props) {
const someProp = heavyCalculation(props.item);
return
}

// 只有 props.item 改变时someProp的值才会被重新计算
function Component(props) {
const someProp = useMemo(() => heavyCalculation(props.item), [props.item]);
return
}
使用React.PureComponent , shouldComponentUpdate
父组件状态的每次更新,都会导致子组件的重新渲染,即使是传入相同props。但是这里的重新渲染不是说会更新DOM,而是每次都会调用diif算法来判断是否需要更新DOM。这对于大型组件例如组件树来说是非常消耗性能的。
在这里我们就可以使用React.PureComponent , shouldComponentUpdate生命周期来确保只有当组件props状态改变时才会重新渲染。如下例子:

export default function ParentComponent(props) {
return (

<SomeComponent someProp={props.somePropValue}


) }
export default function SomeComponent(props) {
return (
{props.someProp}

)
}
// 只要props.somePropValue 发生变化,不论props.someOtherPropValue是否发生变化该组件都会发生变化
export default function AnotherComponent(props) {
return (
{props.someOtherProp}

)
}
我们可以使用React.PureComponent 或shouldComponentUpdate 进行如下优化:
// 第一种优化
class AnotherComponent extends React.PureComponent {
render() {
return

{this.props.someOtherProp}

}
}
//第二种优化
class AnotherComponent extends Component {
shouldComponentUpdate(nextProps) {
return this.props !== nextProps
}
render() {
return

{this.props.someOtherProp}

}
}
PureComponent会进行浅比较来判断组件是否应该重新渲染,对于传入的基本类型props,只要值相同,浅比较就会认为相同,对于传入的引用类型props,浅比较只会认为传入的props是不是同一个引用,如果不是,哪怕这两个对象中的内容完全一样,也会被认为是不同的props。
需要注意的是在对于那些可以忽略渲染时间的组件或者是状态一直变化的组件则要谨慎使用PureComponent,因为进行浅比较也会花费时间,这种优化更适用于大型的展示组件上。大型组件也可以拆分成多个小组件,并使用memo来包裹小组件,也可以提升性能。

避免使用内联对象
使用内联对象时,react会在每次渲染时重新创建对此对象的引用,这会导致接收此对象的组件将其视为不同的对象,因此,该组件对于prop的浅层比较始终返回false,导致组件一直重新渲染。
许多人使用的内联样式的间接引用,就会使组件重新渲染,可能会导致性能问题。为了解决这个问题,我们可以保证该对象只初始化一次,指向相同引用。另外一种情况是传递一个对象,同样会在渲染时创建不同的引用,也有可能导致性能问题,我们可以利用ES6扩展运算符将传递的对象解构。这样组件接收到的便是基本类型的props,组件通过浅层比较发现接受的prop没有变化,则不会重新渲染。示例如下:

// Don’t do this!
function Component(props) {
const aProp = { someProp: ‘someValue’ }
return <AnotherComponent style={{ margin: 0 }} aProp={aProp} />
}

// Do this instead 😃
const styles = { margin: 0 };
function Component(props) {
const aProp = { someProp: ‘someValue’ }
return <AnotherComponent style={styles} {…aProp} />
}
避免使用匿名函数
虽然匿名函数是传递函数的好方法(特别是需要用另一个prop作为参数调用的函数),但它们在每次渲染上都有不同的引用。这类似于上面描述的内联对象。为了保持对作为prop传递给React组件的函数的相同引用,您可以将其声明为类方法(如果您使用的是基于类的组件)或使用useCallback钩子来帮助您保持相同的引用(如果您使用功能组件)。前端培训
当然,有时内联匿名函数是最简单的方法,实际上并不会导致应用程序出现性能问题。这可能是因为在一个非常“轻量级”的组件上使用它,或者因为父组件实际上必须在每次props更改时重新渲染其所有内容。因此不用关心该函数是否是不同的引用,因为无论如何,组件都会重新渲染。

// 避免这样做
function Component(props) {
return <AnotherComponent onChange={() => props.callback(props.id)} />
}

// 优化方法一
function Component(props) {
const handleChange = useCallback(() => props.callback(props.id), [props.id]);
return
}

// 优化方法二
class Component extends React.Component {
handleChange = () => {
this.props.callback(this.props.id)
}
render() {
return
}
}
延迟加载不是立即需要的组件
延迟加载实际上不可见(或不是立即需要)的组件,React加载的组件越少,加载组件的速度就越快。因此,如果您的初始渲染感觉相当粗糙,则可以在初始安装完成后通过在需要时加载组件来减少加载的组件数量。同时,这将允许用户更快地加载您的平台/应用程序。最后,通过拆分初始渲染,您将JS工作负载拆分为较小的任务,这将为您的页面提供响应的时间。这可以使用新的React.Lazy和React.Suspense轻松完成。

// 延迟加载不是立即需要的组件
const MUITooltip = React.lazy(() => import(‘@material-ui/core/Tooltip’));
function Tooltip({ children, title }) {
return (
<React.Suspense fallback={children}>

{children}

</React.Suspense>
);
}

function Component(props) {
return (

)
}
调整CSS而不是强制组件加载和卸载
渲染成本很高,尤其是在需要更改DOM时。每当你有某种手风琴或标签功能,例如想要一次只能看到一个项目时,你可能想要卸载不可见的组件,并在它变得可见时将其重新加载。如果加载/卸载的组件“很重”,则此操作可能非常消耗性能并可能导致延迟。在这些情况下,最好通过CSS隐藏它,同时将内容保存到DOM。
尽管这种方法并不是万能的,因为安装这些组件可能会导致问题(即组件与窗口上的无限分页竞争),但我们应该选择在不是这种情况下使用调整CSS的方法。另外一点,将不透明度调整为0对浏览器的成本消耗几乎为0(因为它不会导致重排),并且应尽可能优先于更该visibility 和 display。
有时在保持组件加载的同时通过CSS隐藏可能是有益的,而不是通过卸载来隐藏。对于具有显著的加载/卸载时序的重型组件而言,这是有效的性能优化手段。
使用React.Fragment避免添加额外的DOM
有些情况下,我们需要在组件中返回多个元素,例如下面的元素,但是在react规定组件中必须有一个父元素。

因此你可能会这样做,但是这样做的话即使一切正常,也会创建额外的不必要的div。这会导致整个应用程序内创建许多无用的元素:

实际上页面上的元素越多,加载所需的时间就越多。为了减少不必要的加载时间,我们可以使React.Fragment来避免创建不必要的元素。

12.说说你对事件循环event loop的理解?


事件循环的执行顺序
任务队列(Event Queue): 所有的任务都可以分为同步任务和异步任务。
同步任务即为 立即执行的任务 ,一般直接进入主线程中执行
异步任务不进入主线程,先放到辅助线程中处理,一般分为"发起函数"和"回调函数"(如setTimeout(fn(), 100)中setTimeout为发起函数,fn为回调函数),先执行发起函数(即下图中的"挂起"),在异步任务有了结果后,将注册的回调函数放到任务队列中,等主线程空闲的时候读取,采用先进先出的机制来进行协调
同步任务和异步任务进入不同的执行环境,同步任务进入主线程栈,异步任务在相应辅助线程中处理完成后,即异步函数达到触发条件了,就把回调函数推入任务队列中,而不是说注册一个异步任务就会被放在这个任务队列中进入任务队列,主线程中的任务执行完毕之后,就会去任务队列中读取对应的任务,推入主线程执行。这个过程就是我们所说的『Event loop (事件循环)』,每一次循环称为一个tick

15.React render方法的原理,在什么时候会触发?

一、原理
在类组件和函数组件中,render函数的形式是不同的。
在类组件中render函数指的就是render方法;而在函数组件中,指的就是整个函数组件。
二、触发时机
render的执行时机主要分成了两部分:
类组件调用 setState 修改状态
函数组件通过useState hook修改状态

16.说说你对vue中mixin的理解?


mixin是面向对象程序设计语言中的类,提供了方法的实现。其他类可以访问mixin类的方法而不必成为其子类;
mixin类通常作为功能模块使用,在需要该功能时“混入”,有利于代码复用又避免了多继承的复杂。
本质就是一个JS对象,可以包含组件中的任意功能选项,如data、components、methods、creaded、computed以及生命周期函数等等。
只需要将共用的功能以对象的方式传入mixins选项中,当组件使用mixins对象时所有mixins对象的对象都将被混入该组件本身的选项中来。

17.for…in循环和for…of循环的区别?


for…of是在for…in之后推出的新特性,弥补for…in中的一些不足.
JSON 数据的标的达方式是key:value
for…of遍历出的结果是value
for…in遍历出的结果是key

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值