字节前端面试被问到的 react 问题
redux 中间件
中间件提供第三方插件的模式,自定义拦截
action
->reducer
的过程。变为action
->middlewares
->reducer
。这种机制可以让我们改变数据流,实现如异步action
,action
过滤,日志输出,异常报告等功能
redux-logger
:提供日志输出redux-thunk
:处理异步操作redux-promise
:处理异步操作,actionCreator
的返回值是promise
React 中 refs 的作用是什么?有哪些应用场景?
Refs 提供了一种方式,用于访问在 render 方法中创建的 React 元素或 DOM 节点。Refs 应该谨慎使用,如下场景使用 Refs 比较适合:
处理焦点、文本选择或者媒体的控制
触发必要的动画
集成第三方 DOM 库
Refs 是使用 React.createRef()
方法创建的,他通过 ref
属性附加到 React 元素上。要在整个组件中使用 Refs,需要将 ref
在构造函数中分配给其实例属性:
由于函数组件没有实例,因此不能在函数组件上直接使用 ref
:
但可以通过闭合的帮助在函数组件内部进行使用 Refs:
注意:
不应该过度的使用 Refs
ref
的返回值取决于节点的类型:当
ref
属性被用于一个普通的 HTML 元素时,React.createRef()
将接收底层 DOM 元素作为他的current
属性以创建ref
。当
ref
属性被用于一个自定义的类组件时,ref
对象将接收该组件已挂载的实例作为他的current
。当在父组件中需要访问子组件中的
ref
时可使用传递 Refs 或回调 Refs。
diff 算法如何比较?
只对同级比较,跨层级的 dom 不会进行复用
不同类型节点生成的 dom 树不同,此时会直接销毁老节点及子孙节点,并新建节点
可以通过 key 来对元素 diff 的过程提供复用的线索
单节点 diff
单点 diff 有如下几种情况:
key 和 type 相同表示可以复用节点
key 不同直接标记删除节点,然后新建节点
key 相同 type 不同,标记删除该节点和兄弟节点,然后新创建节点
如何解决 props 层级过深的问题
使用 Context API:提供一种组件之间的状态共享,而不必通过显式组件树逐层传递 props;
使用 Redux 等状态库。
React 的事件和普通的 HTML 事件有什么不同?
区别:
对于事件名称命名方式,原生事件为全小写,react 事件采用小驼峰;
对于事件函数处理语法,原生事件为字符串,react 事件为函数;
react 事件不能采用 return false 的方式来阻止浏览器的默认行为,而必须要地明确地调用
preventDefault()
来阻止默认行为。
合成事件是 react 模拟原生 DOM 事件所有能力的一个事件对象,其优点如下:
兼容所有浏览器,更好的跨平台;
将事件统一存放在一个数组,避免频繁的新增与删除(垃圾回收)。
方便 react 统一管理和事务机制。
事件的执行顺序为原生事件先执行,合成事件后执行,合成事件会冒泡绑定到 document 上,所以尽量避免原生事件与合成事件混用,如果原生事件阻止冒泡,可能会导致合成事件不执行,因为需要冒泡到 document 上合成事件才会执行。
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 标签默认事件禁掉之后做了什么才实现了跳转?
React-Router 4 的 Switch 有什么用?
Switch 通常被用来包裹 Route,用于渲染与路径匹配的第一个子 <Route>
或 <Redirect>
,它里面不能放其他元素。
假如不加 <Switch>
:
Route 组件的 path 属性用于匹配路径,因为需要匹配 /
到 Home
,匹配 /login
到 Login
,所以需要两个 Route,但是不能这么写。这样写的话,当 URL 的 path 为 “/login” 时,<Route path="/" />
和<Route path="/login" />
都会被匹配,因此页面会展示 Home 和 Login 两个组件。这时就需要借助 <Switch>
来做到只显示一个匹配组件:
此时,再访问 “/login” 路径时,却只显示了 Home 组件。这是就用到了 exact 属性,它的作用就是精确匹配路径,经常与<Switch>
联合使用。只有当 URL 和该 <Route>
的 path 属性完全一致的情况下才能匹配上:
redux 有什么缺点
一个组件所需要的数据,必须由父组件传过来,而不能像 flux 中直接从 store 取
当一个组件相关数据更新时,即使父组件不需要用到这个组件,父组件还是会重新 render,可能会有效率影响,或者需要写复杂的
shouldComponentUpdate
进行判断
react 有什么优点
提高应用性能
可以方便的在客户端和服务端使用
使用 jsx 模板进行数据渲染,可读性好
createElement 过程
React.createElement(): 根据指定的第一个参数创建一个 React 元素
第一个参数是必填,传入的是似 HTML 标签名称,eg: ul, li
第二个参数是选填,表示的是属性,eg: className
第三个参数是选填, 子节点,eg: 要显示的文本内容
Redux 内部原理 内部怎么实现 dispstch 一个函数的
以
redux-thunk
中间件作为例子,下面就是thunkMiddleware
函数的代码
redux-thunk
库内部源码非常的简单,允许action
是一个函数,同时支持参数传递,否则调用方法不变
redux
创建Store
:通过combineReducers
函数合并reducer
函数,返回一个新的函数combination
(这个函数负责循环遍历运行reducer
函数,返回全部state
)。将这个新函数作为参数传入createStore
函数,函数内部通过 dispatch,初始化运行传入的combination
,state 生成,返回 store 对象redux
中间件:applyMiddleware
函数中间件的主要目的就是修改dispatch
函数,返回经过中间件处理的新的dispatch
函数redux
使用:实际就是再次调用循环遍历调用reducer
函数,更新state
什么是 React Context?
Context
通过组件树提供了一个传递数据的方法,从而避免了在每一个层级手动的传递 props
属性。
Dva 工作原理
集成
redux+redux-saga
工作原理
改变发生通常是通过用户交互行为或者浏览器行为(如路由跳转等)触发的,当此类行为会改变数据的时候可以通过
dispatch
发起一个action
,如果是同步行为会直接通过Reducers
改变State
,如果是异步行为(副作用)会先触发Effects
然后流向Reducers
最终改变State
mobox 和 redux 有什么区别?
(1)共同点
为了解决状态管理混乱,无法有效同步的问题统一维护管理应用状态;
某一状态只有一个可信数据来源(通常命名为 store,指状态容器);
操作更新状态方式统一,并且可控(通常以 action 方式提供更新状态的途径);
支持将 store 与 React 组件连接,如 react-redux,mobx- react;
(2)区别 Redux 更多的是遵循 Flux 模式的一种实现,是一个 JavaScript 库,它关注点主要是以下几方面∶
Action∶ 一个 JavaScript 对象,描述动作相关信息,主要包含 type 属性和 payload 属性∶
Reducer∶ 定义应用状态如何响应不同动作(action),如何更新状态;
Store∶ 管理 action 和 reducer 及其关系的对象,主要提供以下功能∶
异步流∶ 由于 Redux 所有对 store 状态的变更,都应该通过 action 触发,异步任务(通常都是业务或获取数据任务)也不例外,而为了不将业务或数据相关的任务混入 React 组件中,就需要使用其他框架配合管理异步任务流程,如 redux-thunk,redux-saga 等;
Mobx 是一个透明函数响应式编程的状态管理库,它使得状态管理简单可伸缩∶
Action∶定义改变状态的动作函数,包括如何变更状态;
Store∶ 集中管理模块状态(State)和动作(action)
Derivation(衍生)∶ 从应用状态中派生而出,且没有任何其他影响的数据
对比总结:
redux 将数据保存在单一的 store 中,mobx 将数据保存在分散的多个 store 中
redux 使用 plain object 保存数据,需要手动处理变化后的操作;mobx 适用 observable 保存数据,数据变化后自动处理响应的操作
redux 使用不可变状态,这意味着状态是只读的,不能直接去修改它,而是应该返回一个新的状态,同时使用纯函数;mobx 中的状态是可变的,可以直接对其进行修改
mobx 相对来说比较简单,在其中有很多的抽象,mobx 更多的使用面向对象的编程思维;redux 会比较复杂,因为其中的函数式编程思想掌握起来不是那么容易,同时需要借助一系列的中间件来处理异步和副作用
mobx 中有更多的抽象和封装,调试会比较困难,同时结果也难以预测;而 redux 提供能够进行时间回溯的开发工具,同时其纯函数以及更少的抽象,让调试变得更加的容易
react-redux 的实现原理?
通过 redux 和 react context 配合使用,并借助高阶函数,实现了 react-redux
React 中 refs 干嘛用的?
Refs
提供了一种访问在render
方法中创建的 DOM 节点或者 React 元素的方法。在典型的数据流中,props
是父子组件交互的唯一方式,想要修改子组件,需要使用新的pros
重新渲染它。凡事有例外,某些情况下咱们需要在典型数据流外,强制修改子代,这个时候可以使用 Refs
。咱们可以在组件添加一个 ref
属性来使用,该属性的值是一个回调函数,接收作为其第一个参数的底层 DOM 元素或组件的挂载实例。
请注意,input
元素有一个ref
属性,它的值是一个函数。该函数接收输入的实际 DOM 元素,然后将其放在实例上,这样就可以在 handleSubmit
函数内部访问它。 经常被误解的只有在类组件中才能使用 refs
,但是refs
也可以通过利用 JS 中的闭包与函数组件一起使用。
根据下面定义的代码,可以找出存在的两个问题吗 ?
请看下面的代码:
答案:1.在构造函数没有将 props
传递给 super
,它应该包括以下行
2.事件监听器(通过addEventListener()
分配时)的作用域不正确,因为 ES6 不提供自动绑定。因此,开发人员可以在构造函数中重新分配clickHandler
来包含正确的绑定:
非嵌套关系组件的通信方式?
即没有任何包含关系的组件,包括兄弟组件以及不在同一个父级中的非兄弟组件。
可以使用自定义事件通信(发布订阅模式)
可以通过 redux 等进行全局状态管理
如果是兄弟组件通信,可以找到这两个兄弟节点共同的父节点, 结合父子间通信方式进行通信。
为什么不直接更新 state
呢 ?
如果试图直接更新 state
,则不会重新渲染组件。
需要使用setState()
方法来更新 state
。它调度对组件state
对象的更新。当state
改变时,组件通过重新渲染来响应:
react16 版本的 reconciliation 阶段和 commit 阶段是什么
reconciliation 阶段包含的主要工作是对 current tree 和 new tree 做 diff 计算,找出变化部分。进行遍历、对比等是可以中断,歇一会儿接着再来。
commit 阶段是对上一阶段获取到的变化部分应用到真实的 DOM 树中,是一系列的 DOM 操作。不仅要维护更复杂的 DOM 状态,而且中断后再继续,会对用户体验造成影响。在普遍的应用场景下,此阶段的耗时比 diff 计算等耗时相对短。
评论