- 目录
- React.js 面试真题
- ★★★ React 事件绑定原理
- ★★★ React 中的 setState 缺点是什么呢
- ★★★ React 组件通信如何实现
- ★★★ 类组件和函数组件的区别
- ★★★ 请你说说 React 的路由是什么?
- ★★★★★ React 有哪些性能优化的手段?
- ★★★★ React hooks 用过吗,为什么要用?
- ★★★★ 虚拟 DOM 的优劣如何?实现原理?
- ★★★★ React 和 Vue 的 diff 时间复杂度从 O(n^3) 优化到 O(n) ,那么 O(n^3) 和 O(n) 是如何计算出来的?
- ★★★ 聊聊 Redux 和 Vuex 的设计思想
- ★★★ React 中不同组件之间如何做到数据交互?
- ★★★ React 中 refs 的作用是什么?
- ★★★★ 请列举 react 生命周期函数。
- ★★★ 组件绑定和 js 原生绑定事件哪个先执行?
- ★★ fetch 的延时操作
- ★★ A 组件嵌套 B 组件,生命周期执行顺序
- ★★★ diff 和 Key 之间的联系
- ★★★ 虚拟 dom 和原生 dom
- ★★★★ 新出来两个钩子函数?和砍掉的 will 系列有啥区别?
- ★★★ react 中如何打包上传图片文件
- ★★★ 对单向数据流和双向数据绑定的理解,好处?
- ★★ React 组件中 props 和 state 有什么区别?
- ★★ react 中组件分为那俩种?
- ★★ react 中函数组件和普通组件的区别?
- ★★★★ react 中 setState 之后做了什么?
- ★★★★ redux 本来是同步的,为什么它能执行异步代码?中间件的实现原理是什么?
- ★★★★ 列举重新渲染 render 的情况
- ★★★ React 按需加载
- ★★★ React 实现目录树(组件自身调用自身)
- ★★★ React 组件生命周期按装载,更新,销毁三个阶段分别都有哪些?
- ★★★★★ 调用 this.setState 之后,React 都做了哪些操作?怎么拿到改变后的值?
- ★★★ 如果我进行三次 setState 会发生什么
- ★★★ 循环执行 setState 组件会一直重新渲染吗?为什么?
- ★★★ 渲染一个 react 组件的过程
- ★★★ React 类组件,函数组件,在类组件修改组件对象会使用。
- ★★★★ 类组件怎么做性能优化?函数组件怎么做性能优化?
- ★★★ useEffect 和 useLayoutEffect 的区别
- ★★★ hooks 的使用有什么注意事项
- ★★★ 纯函数有什么特点,副作用函数特点
- ★★ React 中 refs 干嘛用的?如何创建 refs?
- ★★★ 在构造函数调用
super
并将props
作为参数传入的作用是啥? - ★★★ 如何 React.createElement ?
- ★★★ 讲讲什么是 JSX ?
- ★★★ 为什么不直接更新
state
呢? - ★★★ React 组件生命周期有哪些不同阶段?React 的生命周期方法有哪些?
- ★★★ 这三个点(…)在 React 干嘛用的?
- ★★★ React 中的
useState()
是什么? - ★★★ React 中的 StrictMode(严格模式)是什么?
- ★★★ 为什么类方法需要绑定到类实例?
- ★★★★ 什么是 prop drilling,如何避免?
- ★★ 描述 Flux 与 MVC?
- ★★★ 这段代码有什么问题吗?
- ★★★★ 什么是 React Context?
- ★★★★★ 什么是 React Fiber?
- ★★★ 如何在 React 的 Props 上应用验证?
- ★★ 在 React 中使用构造函数和 getInitialState 有什么区别?
- ★★★ 如何有条件地向 React 组件添加属性?
- ★★★★ Hooks 会取代
render props
和高阶组件吗? - ★★★ 如何避免组件的重新渲染?
- ★★★ 什么是纯函数?
- ★★★★ 当调用
setState
时,Reactrender
是如何工作的? - ★★★ 如何避免在 React 重新绑定实例?
- ★★★ 在 js 原生事件中 onclick 和 jsx 里 onclick 的区别
- ★★★★ diff 复杂度原理及具体过程画图
- ★★★★ shouldComponentUpdate 的作用是什么?
- ★★★ React 组件间信息传递
- ★★★ React 状态管理工具有哪些?redux actionCreator 都有什么?
- ★★★★ 什么是高阶组件、受控组件及非受控组件?都有啥区别
- ★★★ vuex 和 redux 的区别?
- ★★★ Redux 遵循的三个原则是什么?
- ★★★ React 中的 keys 的作用是什么?
- ★★★ redux 中使用 setState 不能立刻获取值,怎么办
- ★★ 什么是 JSX
- ★★★ React 新老版生命周期函数
- ★★★★ vue react 都怎么检测数据变化
- ★★★ React 中怎么让 setState 同步更新?
- ★★★★ 什么是 immutable?为什么要使用它?
- ★★★ 为什么不建议在 componentWillMount 做 AJAX 操作
- ★★★★ 如何在 React 中构建一个弹出的遮罩层
- ★★★★★ React 中的 Context 的使用
- ★★★★ React 路由懒加载的实现
- ★★★★ React-router-dom 内部是怎么样实现的,怎么做路由守卫?
- ★★★★ redux 中 sages 和 thunk 中间件的区别,优缺点
- ★★ 为什么说 React 是 view(视图层)
- ★★★ 怎么用 useEffect 模拟生命周期函数?
- ★★★ useCallback 是干什么的?使用 useCallback 有什么好处?
- ★★★ 能简单说一下 redux-sage 的使用流程吗?
- ★★★★ React 复用组件的状态和增强功能的方法
- ★★★ redux 和 mobx 的区别
- ★★★ react 中如何实现命名插槽
- ★★★ 简单说一下,如何在 react 中实现瀑布流加载?(左右两列的一个商品长列表)
目录
★★★★ React 和 Vue 的 diff 时间复杂度从 O(n^3) 优化到 O(n) ,那么 O(n^3) 和 O(n) 是如何计算出来的?
★★★★ 新出来两个钩子函数?和砍掉的 will 系列有啥区别?
★★ React 组件中 props 和 state 有什么区别?
★★★★ redux 本来是同步的,为什么它能执行异步代码?中间件的实现原理是什么?
★★★ React 组件生命周期按装载,更新,销毁三个阶段分别都有哪些?
★★★★★ 调用 this.setState 之后,React 都做了哪些操作?怎么拿到改变后的值?
★★★ 循环执行 setState 组件会一直重新渲染吗?为什么?
★★★ React 类组件,函数组件,在类组件修改组件对象会使用。
★★★ useEffect 和 useLayoutEffect 的区别
★★ React 中 refs 干嘛用的?如何创建 refs?
★★★ 在构造函数调用 super
并将 props
作为参数传入的作用是啥?
★★★ React 组件生命周期有哪些不同阶段?React 的生命周期方法有哪些?
★★★ React 中的 StrictMode(严格模式)是什么?
this.setState((prevState, props) => {
return {
streak: prevState.streak + props.count,
}
})
★★ 在 React 中使用构造函数和 getInitialState 有什么区别?
★★★★ Hooks 会取代 render props
和高阶组件吗?
★★★★ 当调用setState
时,React render
是如何工作的?
★★★ 在 js 原生事件中 onclick 和 jsx 里 onclick 的区别
★★★★ shouldComponentUpdate 的作用是什么?
★★★ React 状态管理工具有哪些?redux actionCreator 都有什么?
★★★ redux 中使用 setState 不能立刻获取值,怎么办
★★★ 为什么不建议在 componentWillMount 做 AJAX 操作
★★★★ React-router-dom 内部是怎么样实现的,怎么做路由守卫?
★★★★ redux 中 sages 和 thunk 中间件的区别,优缺点
★★★ useCallback 是干什么的?使用 useCallback 有什么好处?
★★★ 简单说一下,如何在 react 中实现瀑布流加载?(左右两列的一个商品长列表)
React.js 面试真题
★★★ React 事件绑定原理
/*
一、react并没有使用原生的浏览器事件,而是在基于Virtual DOM的基础上实现了合成事件,采用小驼峰命名法,默认的事件传播方式是冒泡,如果想改为捕获的话,直接在事件名后面加上Capture即可;事件对象event也不是原生事件对象,而是合成对象,但通过nativeEvent属性可以访问原生事件对象;
二、react合成事件主要分为以下三个过程:
1、事件注册
在该阶段主要做了两件事:document上注册、存储事件回调。所有事件都会注册到document上,拥有统一的回调函数dispatchEvent来执行事件分发,类似于document.addEventListener("click",dispatchEvent)。
register:
addEventListener-click
addEventListener-change
listenerBank:
{
click: {key1: fn1, key2: fn2},
change: {key1: fn3, key3: fn4}
}
2、事件合成
事件触发后,会执行一下过程:
(1)进入统一的事件分发函数dispatchEvent;
(2)找到触发事件的 ReactDOMComponent;
(3)开始事件的合成;
—— 根据当前事件类型生成指定的合成对象
—— 封装原生事件和冒泡机制
—— 查找当前元素以及他所有父级
—— 在listenerBank根据key值查找事件回调并合成到 event(合成事件结束)
3、批处理
批量处理合成事件内的回调函数
*/
★★★ React 中的 setState 缺点是什么呢
/*
setState执行的时候可以简单的认为,隶属于原生js执行的空间,那么就是属于同步,被react处理过的空间属于异步,这其实也是一种性能的优化,如果多次使用setState修改值,那么在异步中会先进行合并,再进行渲染,降低了操作dom的次数,具体如下:
(1)setState 在合成事件和钩子函数中是“异步”的,在原生事件和 `setTimeout` 中都是同步的。
(2)setState的“异步”并不是说内部由异步代码实现,其实本身执行的过程和代码都是同步的,只是合成事件和钩子函数的调用顺序在更新之前,导致在合成事件和钩子函数中没法立马拿到更新后的值,形式了所谓的“异步”,当然可以通过第二个参数 setState(partialState, callback) 中的callback拿到更新后的结果。
(3)setState 的批量更新优化也是建立在“异步”(合成事件、钩子函数)之上的,在原生事件和setTimeout 中不会批量更新,在“异步”中如果对同一个值进行多次 setState, setState 的批量更新策略会对其进行覆盖,取最后一次的执行,如果是同时 setState 多个不同的值,在更新时会对其进行合并批量更新。
(4)正是由于setState存在异步的机制,如果setState修改值的时候依赖于state本身的值,有时候并不可靠,这时候我们需要传入一个回调函数作为其入参,这个回调函数的第一个参数为更新前的state值。
*/
★★★ React 组件通信如何实现
/*
react本身:
(1)props——父组件向子组件通过props传参
(2)实例方法——在父组件中可以用 refs 引用子组件,之后就可以调用子组件的实例方法了
(3)回调函数——用于子组件向父组件通信,子组件调用props传递过来的方法
(4)状态提升——两个子组件可以通过父组件定义的参数进行传参
(5)Context上下文——一般用作全局主题
(6)Render Props——渲染的细节由父组件控制
状态管理:
(1) mobx/redux/dva——通过在view中触发action,改变state,进而改变其他组件的view
*/
★★★ 类组件和函数组件的区别
/*
(1)语法上:函数组件是一个函数,返回一个jsx元素,而类组件是用es6语法糖class定义,继承component这个类
(2)类组件中可以通过state进行状态管理,而在函数组件中不能使用setState(),在react16.8以后,函数组件可以通过hooks中的useState来模拟类组件中的状态管理;
(3)类组件中有一系列的生命周期钩子函数,在函数组件中也需要借助hooks来使用生命周期函数;
(4)类组件能够捕获**最新**的值(永远保持一致),这是因为当实例的props属性发生修改时,class组件能够直接通过this捕获到组件最新的props;而函数式组件是捕获**渲染**所使用的值,已经因为javascript**闭包**的特性,之前的props参数保存在内存之中,无法从外部进行修改。
*/
★★★ 请你说说 React 的路由是什么?
/*
路由分为前端路由和后端路由,后端路由是服务器根据用户发起的请求而返回不同内容,前端路由是客户端根据不同的URL去切换组件;在web应用前端开发中,路由系统是最核心的部分,当页面的URL发生改变时,页面的显示结果可以根据URL的变化而变化,但是页面不会刷新。
react生态中路由通常是使用react-router来进行配置,其主要构成为:
(1)Router——对应路由的两种模式,包括<BrowsersRouter>与<HashRouter>;
(2)route matching组件——控制路径对应的显示组件,可以进行同步加载和异步加载,<Route>;
(3)navigation组件——用做路由切换和跳转,<Link>;
BrowserRouter与HashRouter的区别:
(1)底层原理不一样:BrowserRouter使用的是H5的history API,不兼容IE9及以下版本;HashRouter使用的是URL的哈希值;
(2)path表现形式不一样:BrowserRouter的路径中没有#,例如:localhost:3000/demo/test;HashRouter的路径包含#,例如:localhost:3000/#/demo/test;
(3)刷新后对路由state参数的影响:BrowserRouter没有任何影响,因为state保存在history对象中;HashRouter刷新后会导致路由state参数的丢失;
*/
★★★★★ React 有哪些性能优化的手段?
/*
1、使用纯组件;
2、使用 React.memo 进行组件记忆(React.memo 是一个高阶组件),对于相同的输入,不重复执行;
3、如果是类组件,使用 shouldComponentUpdate(这是在重新渲染组件之前触发的其中一个生命周期事件)生命周期事件,可以利用此事件来决定何时需要重新渲染组件;
4、路由懒加载;
5、使用 React Fragments 避免额外标记;
6、不要使用内联函数定义(如果我们使用内联函数,则每次调用“render”函数时都会创建一个新的函数实例);
7、避免在Willxxx系列的生命周期中进行异步请求,操作dom等;
8、如果是类组件,事件函数在Constructor中绑定bind改变this指向;
9、避免使用内联样式属性;
10、优化 React 中的条件渲染;
11、不要在 render 方法中导出数据;
12、列表渲染的时候加key;
13、在函数组件中使用useCallback和useMemo来进行组件优化,依赖没有变化的话,不重复执行;
14、类组件中使用immutable对象;
*/
★★★★ React hooks 用过吗,为什么要用?
/*
Hooks 是React在16.8版本中出的一个新功能,本质是一种函数,可以实现组件逻辑复用,让我们在函数式组件中使用类组件中的状态、生命周期等功能,hooks的名字都是以use开头。
react:
1、useState——创建状态
接收一个参数作为初始值;返回一个数组,第一个值为状态,第二个值为改变状态的函数
2、useEffect——副作用(数据获取、dom操作影响页面——在渲染结束之后执行
(1)第一个参数为函数,第二个参数为依赖列表,只有依赖更新时才会执行函数;返回一个函数,当页面刷新的时候先执行返回函数再执行参数函数
(2)如果不接受第二个参数,那么在第一次渲染完成之后和每次更新渲染页面的时候,都会调用useEffect的回调函数
3、useRef
返回一个可变的ref对象,此索引在整个生命周期中保持不变。可以用来获取元素或组件的实例,用来做输入框的聚焦或者动画的触发。
4、useMemo——优化函数组件中的功能函数——在渲染期间执行
(1)接收一个函数作为参数,同样接收第二个参数作为依赖列表,返回值可以是任何,函数、对象等都可以
(2)这种优化有助于避免在每次渲染时都进行高开销的计算,仅会在某个依赖项改变时才重新计算
5、useContext——获取上下文注入的值
(1)接受一个context 对象,并返回该对象<MyContext.Provider> 元素的 value值;
const value = useContext(MyContext);
6、useLayoutEffect——有DOM操作的副作用——在DOM更新之后执行
和useEffet类似,但是执行时机不同,useLayoutEffect在DOM更新之后执行,useEffect在render渲染结束后执行,也就是说useLayoutEffect比useEffect先执行,这是因为DOM更新之后,渲染才结束或者渲染还会结束
7、useCallback——与useMemo类似
useMemo与useCallback相同,接收一个函数作为参数,也同样接收第二个参数作为依赖列表;useCallback是对传过来的回调函数优化,返回的是一个函数
react-router:
被route包裹的组件,可以直接使用props进行路由相关操作,但是没有被route包裹的组件只能用withRouter高阶组件修饰或者使用hooks进行操作
1、useHistory——跳转路由
2、useLocation——得到url对象
3、useParams——得到url上的参数
react-redux:
1、useSelector——共享状态——从redux的store中提取数据
2、useDispatch——共享状态——返回redux的store中对dispatch的引用
*/
★★★★ 虚拟 DOM 的优劣如何?实现原理?
/*
虚拟dom是用js模拟一颗dom树,放在浏览器内存中,相当于在js和真实dom中加了一个缓存,利用dom diff算法避免了没有必要的dom操作,从而提高性能。
优点:
(1)虚拟DOM具有批处理和高效的Diff算法,最终表现在DOM上的修改只是变更的部分,可以保证非常高效的渲染,优化性能;
(2)虚拟DOM不会立马进行排版与重绘操作,对虚拟DOM进行频繁修改,最后一次性比较并修改真实DOM中需要改的部分;
(3)虚拟 DOM 有效降低大面积真实 DOM 的重绘与排版,因为最终与真实 DOM 比较差异,可以只渲染局部;
缺点:
(1)首次渲染大量DOM时,由于多了一层虚拟DOM的计算,会比innerHTML插入慢;
React组件的渲染过程:
(1)使用JSX编写React组件后所有的JSX代码会通过Babel转化为 React.createElement执行;
(2)createElement函数对 key和 ref等特殊的 props进行处理,并获取 defaultProps对默认 props进行赋值,并且对传入的子节点进行处理,最终构造成一个 ReactElement对象(所谓的虚拟 DOM)。
(3)ReactDOM.render将生成好的虚拟 DOM渲染到指定容器上,其中采用了批处理、事务等机制并且对特定浏览器进行了性能优化,最终转换为真实 DOM。
虚拟DOM的组成——ReactElementelement对象结构:
(1)type:元素的类型,可以是原生html类型(字符串),或者自定义组件(函数或class)
(2)key:组件的唯一标识,用于Diff算法,下面会详细介绍
(3)ref:用于访问原生dom节点
(4)props:传入组件的props,chidren是props中的一个属性,它存储了当前组件的孩子节点,可以是数组(多个孩子节点)或对象(只有一个孩子节点)
(5)owner:当前正在构建的Component所属的Component
(6)self:(非生产环境)指定当前位于哪个组件实例
(7)_source:(非生产环境)指定调试代码来自的文件(fileName)和代码行数(lineNumber)
*/
★★★★ React 和 Vue 的 diff 时间复杂度从 O(n^3) 优化到 O(n) ,那么 O(n^3) 和 O(n) 是如何计算出来的?
/*
react的diff算法只需要O(n),这是因为react对树节点的比较做了一些前提假设,限定死了一些东西,不做过于复杂的计算操作,所以降低了复杂度。react和vue做了以下的假设,这样的话diff运算时只进行同层比较,每一个节点只遍历了一次。
(1)Web UI中DOM节点跨层级的移动操作特别少,可以忽略不计;
(2)拥有相同类的两个组件将会生成相似的树形结构,拥有不同类的两个组件将会生成不同的树形结构;
(3)对于同一层级的一组子节点,它们可以通过唯一 id 进行区分。
而传统的diff运算时间复杂度为O(n^3),这是因为传统的树节点要做非常完整的检查,首先需要节点之间需要两两比较,找到所有差异,这个对比过程时间复杂度为O(n^2),找到差异后还要计算出最小的转换方式,最终复杂度为O(n^3)
*/
★★★ 聊聊 Redux 和 Vuex 的设计思想
/*
Flux的核心思想就是数据和逻辑永远单向流动,由三大部分组成 dispatcher(负责分发事件), store(负责保存数据,同时响应事件并更新数据)和 view(负责订阅store中的数据,并使用这些数据渲染相应的页面),Redux和Vuex是flux思想的具体实现,都是用来做状态管理的工具,Redux主要在react中使用,Vuex主要在vue中使用。
Redux设计和使用的三大原则:
(1)单一的数据源:整个应用的 state被储存在唯一一个 store中;
(2)状态是只读的:Store.state不能直接修改(只读),必须调用dispatch(action) => store.reducer => return newState;action是一个对象,有type(操作类型)和payload(新值)属性;
(3)状态修改均由纯函数完成:在Redux中,通过纯函数reducer来确定状态的改变,因为reducer是纯函数,所以相同的输入,一定会得到相同的输出,同时也不支持异步;返回值是一个全新的state;
vuex由State + Muatations(commit) + Actions(dispatch) 组成:
(1)全局只有一个Store实例(单一数据源);
(2)Mutations必须是同步事务,不同步修改的话,会很难调试,不知道改变什么时候发生,也很难确定先后顺序,A、B两个mutation,调用顺序可能是A -> B,但是最终改变 State的结果可能是B -> A;
(3)Actions负责处理异步事务,然后在异步回调中触发一个或多个mutations,也可以在业务代码中处理异步事务,然后在回调中同样操作;
(4)模块化通过module方式来处理,这个跟Redux-combineReducer类似,在应用中可以通过namespaceHelper来简化使用;
*/
★★★ React 中不同组件之间如何做到数据交互?
★★★ React 中 refs 的作用是什么?
// ref是React提供的用来操纵React组件实例或者DOM元素的接口。主要用来做文本框的聚焦、触发强制动画等;
// 类组件
class Foo extends React.Component {
constructor(props) {
super(props)
this.myRef = React.createRef()
}
render() {
return
;<div>
<input ref={this.myRef} />
<button onClick={() => this.handle()}>聚焦</button>
</div>
}
handle() {
// 通过current属性访问到当前元素
this.myRef.current.focus()
}
}
// 函数组件
function Foo() {
const inputEl = useRef(null)
const handle = () => {
inputEl.current.focus()
}
return
;<div>
<input type="text" ref={inputEl} />
<button onClick={handle}>聚焦</button>
</div>
}
★★★★ 请列举 react 生命周期函数。
/*
第一阶段:装载阶段3
constructor()
render()
componentDidMount()
第二阶段:更新阶段2
[shouldComponentUpdate()]
render()
componentDidUpdate()
第三阶段:卸载阶段1
componentWillUnmount()
constructor生命周期:
(1)当react组件实例化时,是第一个运行的生命周期;
(2)在这个生命周期中,不能使用this.setState();
(3)在这个生命周期中,不能使用副作用(调接口、dom操作、定时器、长连接等);
(4)不能把props和state交叉赋值;
componentDidMount生命周期:
(1)相当于是vue中的mounted;
(2)它表示DOM结构在浏览器中渲染已完成;
(3)在这里可以使用任何的副作用;
shouldComponentUpdate(nextProps,nextState)生命周期:
(1)相当于一个开关,如果返回true则更新机制正常执行,如果为false则更新机制停止;
(2)在vue中是没有的;
(3)存在的意义:可以用于性能优化,但是不常用,最新的解决方案是使用PureComponent;
(4)理论上,这个生命周期的作用,用于精细地控制声明式变量的更新问题,如果变化的声明式变量参与了视图渲染则返回true,如果被变化的声明式变量没有直接或间接参与视图渲染,则返回false;
componentDidUpdate生命周期:
(1)相当于vue中的updated();
(2)它表示DOM结构渲染更新已完成,只发生在更新阶段;
(3)在这里,可以执行大多数的副作用,但是不建议;
(4)在这里,可以使用this.setState(),但是要有终止条件判断。
componentWillUnmount生命周期:
(1)一般在这里清除定时器、长连接等其他占用内存的构造器;
render生命周期:
(1)render是类组件中唯一必须有的生命周期,同时必须有return(return 返回的jsx默认只能是单一根节点,但是在fragment的语法支持下,可以返回多个兄弟节点);
(2)Fragment碎片写法: <React.Fragment></React.Fragment> 简写成<></>;
(3)return之前,可以做任意的业务逻辑,但是不能使用this.setState(),会造成死循环;
(4)render()在装载阶段和更新阶段都会运行;
(5)当render方法返回null的时候,不会影响生命周期函数的正常执行。
*/
★★★ 组件绑定和 js 原生绑定事件哪个先执行?
// 先执行js原生绑定事件,再执行合成事件,因为合成事件是发生在冒泡阶段
★★ fetch 的延时操作
// fetch语法:fetch(resource, config).then( function(response) { ... } );resource为要获取的资源,config是配置对象,包含method请求方法,headers请求头信息等
// 定义一个延时函数,返回一个promise
const delayPromise = (timeout = 5000) => {
return new Promise((resolve, reject) => {
setTimeout(() => {
reject(new Error("网络错误"))
}, timeout)
})
}
// 定义一个fetch网络请求,返回一个promise
const fetchPromise = (resource, config) => {
return new Promise((resolve, reject) => {
fetch(resource, config).then((res) => {
resolve(res)
})
})
}
// promise的race静态方法接受多个promise对象组成的数组,该数组中哪个promise先执行完成,race方法就返回这个promise的执行结果
const fetchRequest = (resource, config, timeout) => {
Promise.race([delayPromise(timeout), fetchPromise(resource, config)])
}
★★ A 组件嵌套 B 组件,生命周期执行顺序
/*
父组件创建阶段的生命周期钩子函数 constructor
父组件创建阶段的生命周期钩子函数 render
子组件创建阶段的生命周期钩子函数 constructor
子组件创建阶段的生命周期钩子函数 render
子组件创建阶段的生命周期钩子函数 componentDidMount
父组件创建阶段的生命周期钩子函数 componentDidMount
*/
★★★ diff 和 Key 之间的联系
/*
diff算法即差异查找算法,对于DOM结构即为tree的差异查找算法,只有在React更新阶段才会有Diff算法的运用;react的diff运算为了降低时间复杂度,是按层比较新旧两个虚拟dom树的。diff运算的主要流程见下:
1、tree diff : 新旧两棵dom树,逐层对比的过程就是 tree diff, 当整棵DOM树逐层对比完毕,则所有需要被按需更新的元素,必然能够被找到。
2、component diff : 在进行tree diff的时候,每一层中,都有自己的组件,组件级别的对比,叫做 component diff。如果对比前后,组件的类型相同,则暂时认为此组件不需要更新;如果对比前后,组件的类型不同,则需要移除旧组件,创建新组件,并渲染到页面上。
React只会匹配类型相同的组件,也就是说如果<A>被<B>替换,那么React将直接删除A组件然后创建一个B组件;如果某组件A转移到同层B组件上,那么这个A组件会先被销毁,然后在B组件下重新生成,以A为根节点的树整个都被重新创建,这会比较耗费性能,但实际上我们很少跨层移动dom节点,一般都是同层横向移动;
3、element diff :在进行组件对比的时候,如果两个组件类型相同,则需要进行元素级别的对比,这叫做element diff。
对于列表渲染,react会在创建时要求为每一项输入一个独一无二的key,这样就能进行高效的diff运算了。比如我们要在b和c节点中间插入一个节点f,jquery会将f这个节点后面的每一个节点都进行更新,比如c更新成f,d更新成c,e更新成d,这样操作的话就会特别多,而加了key的react咋不会频繁操作dom,而是优先采用移动的方式,找到正确的位置去插入新节点;所以我们不能省略key值,因为在对比两个新旧的子元素是,是通过key值来精确地判断两个节点是否为同一个,如果没有key的话则是见到谁就更新谁,非常耗费性能。
当我们通过this.setState()改变数据的时候,React会将其标记为脏节点,在事件循环的最后才会重新渲染所有的脏节点以及脏节点的子树;另外我们可以使用shouldComponentUpdate这个生命周期来选择性的渲染子树,可以基于组件之前的状态或者下一个状态来决定它是否需要重新渲染,这样的话可以组织重新渲染大的子树。
*/
★★★ 虚拟 dom 和原生 dom
/*
(1)原生dom是浏览器通过dom树渲染的复杂对象,属性非常多;
(2)虚拟dom是存在于内存中的js对象,属性远少于原生的dom对象,它用来描述真实的dom,并不会直接在浏览器中显示;
(3)原生dom操作、频繁排版与重绘的效率是相当低的,虚拟dom则是利用了计算机内存高效的运算性能减少了性能的损耗;
(4)虚拟DOM进行频繁修改,然后一次性比较并修改真实DOM中需要改的部分,最后并在真实DOM中对修改部分进行排版与重绘,减少过多DOM节点排版与重绘损耗
*/
★★★★ 新出来两个钩子函数?和砍掉的 will 系列有啥区别?
// react16 中废弃了三个钩子
componentWillMount // 组件将要挂载的钩子
componentWillReceiveProps // 组件将要接收一个新的参数时的钩子
componentWillUpdate // 组件将要更新的钩子
// 新增了方法
getDerivedStateFromProps // 静态方法
getSnapshotBeforeUpdate
/*
在16.8版本以后,react将diff运算改进为Fiber,这样的话当我们调用setState方法进行更新的时候,在reconciler 层中js运算会按照节点为单位拆分成一个个小的工作单元,在render前可能会中断或恢复,就有可能导致在render前这些生命周期在进行一次更新时存在多次执行的情况,此时如果我们在里面使用ref操作dom的话,就会造成页面频繁重绘,影响性能。
所以废弃了这几个will系列的勾子,增加了 getDerivedStateFromProps这个静态方法,这样的话我们就不能在其中使用this.refs以及this上的方法了;getSnapshotBeforeUpdate 这个方法已经到了commit阶段,只会执行一次,给想读取 dom 的用户一些空间。
*/
★★★ react 中如何打包上传图片文件
★★★ 对单向数据流和双向数据绑定的理解,好处?
/*
react的单向数据流是指只允许父组件向子组件传递数据,子组件绝对不能修改父组件传的数据,如果想要修改数据,则要在子组件中执行父组件传递过来的回调函数,提醒父组件对数据进行修改。数据单向流让所有的状态改变可以追溯,有利于应用的可维护性;
angular中实现了双向数据绑定,代码编写方便,但是不利于维护
*/
★★ React 组件中 props 和 state 有什么区别?
/*
1、props是从外部传入组件的参数,一般用于父组件向子组件通信,在组件之间通信使用;state一般用于组件内部的状态维护,更新组建内部的数据,状态,更新子组件的props等
2、props不可以在组件内部修改,只能通过父组件进行修改;state在组件内部通过setState修改;
*/
★★ react 中组件分为那俩种?
// 类组件和函数组件
★★ react 中函数组件和普通组件的区别?
见上
★★★★ react 中 setState 之后做了什么?
/*
如果是在隶属于原生js执行的空间,比如说setTimeout里面,setState是同步的,那么每次执行setState将立即更新this.state,然后触发render方法,渲染数据;
如果是在被react处理过的空间执行,比如说合成事件里,此时setState是异步执行的,并不会立即更新this.state的值,当执行setState的时候,会将需要更新的state放入状态队列,在这个空间最后再合并修改this.state,触发render;
*/
★★★★ redux 本来是同步的,为什么它能执行异步代码?中间件的实现原理是什么?
/*
当我们需要修改store中值的时候,我们是通过 dispatch(action)将要修改的值传到reducer中的,这个过程是同步的,如果我们要进行异步操作的时候,就需要用到中间件;中间件其实是提供了一个分类处理action的机会,在 middleware 中,我们可以检阅每一个流过的action,并挑选出特定类型的 action进行相应操作,以此来改变 action;
applyMiddleware 是个三级柯里化的函数。它将陆续的获得三个参数:第一个是 middlewares 数组,第二个是 Redux 原生的 createStore,最后一个是 reducer;然后applyMiddleware会将不同的中间件一层一层包裹到原生的 dispatch 之上;
redux-thunk 中间件的作用就是让我们可以异步执行redux,首先检查参数 action 的类型,如果是函数的话,就执行这个 action这个函数,并把 dispatch, getState, extraArgument 作为参数传递进去,否则就调用next让下一个中间件继续处理action。
*/
// 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
★★★★ 列举重新渲染 render 的情况
this.setState()
this.forceUpdate()
// 接受到新的props
// 通过状态管理,mobx、redux等
// 改变上下文
★★★ React 按需加载
// 1、使用React.lazy, 但是React.lazy技术还不支持服务端渲染
const OtherComponent = React.lazy(() => import("./OtherComponent"))
// 2、使用Loadable Components这个库
import loadable from "@loadable/component"
const OtherComponent = loadable(() => import("./OtherComponent"))
★★★ React 实现目录树(组件自身调用自身)
★★★ React 组件生命周期按装载,更新,销毁三个阶段分别都有哪些?
见上
★★★★★ 调用 this.setState 之后,React 都做了哪些操作?怎么拿到改变后的值?
/*
如果是在隶属于原生js执行的空间,比如说setTimeout里面,setState是同步的,那么每次执行setState将立即更新this.state,然后触发render方法;因为是同步执行,可以直接获取改变后的值;
如果是在被react处理过的空间执行,比如说合成事件里,此时setState是异步执行的,并不会立即更新this.state的值,当执行setState的时候,会将需要更新的state放入状态队列,在这个空间最后再合并修改this.state,触发render;setState接受第二个参数,是一个回调函数,可以在这里获取改变后的state值;
触发render执行后,会生成一个新的虚拟dom结构,然后触发diff运算,找到变化的地方,重新渲染;
*/
★★★ 如果我进行三次 setState 会发生什么
// 看情况,如果是在原生js空间,则会同步执行,修改三次state的值,调用三次render函数;如果是在react函数空间下,则会进行合并,只修改一次state的值,调用一次render。
★★★ 循环执行 setState 组件会一直重新渲染吗?为什么?
见上
★★★ 渲染一个 react 组件的过程
/*
1、babel编译
当我们对代码进行编译的时候,babel会将我们在组件中编写的jsx代码转化为React.createElement的表达式,createElement方法有三个参数,分别为type(元素类型)、attributes(元素所有属性)、children(元素所有子节点);
2、生成element
当render方法被触发以后,createElement方法会执行,返回一个element对象,这个对象描述了真实节点的信息,其实就是虚拟dom节点;
3、生成真实节点(初次渲染)
这时候我们会判断element的类型,如果是null、false则实例一个ReactDOMEmptyComponent对象; 是string、number类型的话则实例一个ReactDOMTextComponent对象; 如果element是对象的话,会进一步判断type元素类型,是原生dom元素,则实例化ReactDOMComponent; 如果是自定义组件,则实例化ReactCompositeComponentWrapper;
在这些类生成实例对象的时候,在其内部会调用 mountComponent方法,这个方法里面有一系列浏览器原生dom方法,可以将element渲染成真实的dom并插入到文档中;
4、生命周期
componentDidMount:会在组件挂载后(插入DOM树中) 立即调用。一般可以在这里请求数据;
componentDidUpdate:会在数据更新后立即调用,首次渲染不会执行此方法;可以在其中直接调用 setState,但必须用if语句进行判断,防止死循环;
conponentWillUnmount:会在组件卸载及销毁之前调用,在此方法中执行必要的清理操作,如清除timer;
static getDerivedStateFromProps(prps,state):这个生命周期函数代替了componentWillMount和componentWillUpdate生命周期;props和state发生改变则调用,在初始化挂载及后续更新时都会被调用,返回一个对象来更新state,如果返回null则不更新任何内容;
shouldComponentUpdate(nextProps,nextState):这个生命周期函数的返回值用来判断React组件是否因为当前 state 或 props 更改而重新渲染,默认返回值是true;这个方法在初始化渲染或使用forceUpdate()时不会调用;当将旧的state的值原封不动赋值给新的state(即不改变state的值,但是调用了setState)和 无数据交换的父组件的重新渲染都会导致组件重新渲染,这时候都可以通过shouldComponentUpdate来优化。
*/
★★★ React 类组件,函数组件,在类组件修改组件对象会使用。
★★★★ 类组件怎么做性能优化?函数组件怎么做性能优化?
/*
类组件:
(1)使用shouldComponentUpdate:这个生命周期可以让我们决定当前状态或属性的改变是否重新渲染组件,默认返回ture,返回false时不会执行render,在初始化渲染或使用forceUpdate()时不会调用;如果在shouldComponentUpdate比较的值是引用类型的话,可能达不到我们想要的效果,因为引用类型指向同一个地址;
当将旧的state的值原封不动赋值给新的state(即不改变state的值,但是调用了setState)和 无数据交换的父组件的重新渲染都会导致组件重新渲染,这时候都可以通过shouldComponentUpdate来优化;
(2)React.PureComponent:基本上和Component用法一致,不同之处在于 PureComponent不需要开发者自己设置shouldComponentUpdate,因为PureComponent自带通过props和state的浅对比来实现 shouldComponentUpate;但是如果props和state对象包含复杂的数据结构,它可能会判断错误(表现为对象深层的数据已改变,视图却没有更新);
(4)使用Immutable:immutable是一种持久化数据,一旦被创建就不会被修改,修改immutable对象的时候返回新的immutable;也就是说在使用旧数据创建新数据的时候,会保证旧数据同时可用且不变;为了避免深度复制所有节点的带来的性能损耗,immutable使用了结构共享,即如果对象树中的一个节点发生变化,只修改这个节点和受他影响的父节点,其他节点仍然共享;
(5)bind函数:在react中改变this的指向有三种方法,a)constructor中用bind绑定; b)使用时通过bind绑定; 3)使用箭头函数;选择第一种只在组件初始化的时候执行一次,第二种组件在每次render都要重新绑定,第三种在每次render时候都会生成新的箭头函数,所以选择第一种;
函数组件:
(1)useCallback:接收一个函数作为参数,接收第二个参数作为依赖列表,返回值为函数,有助于避免在每次渲染时都进行高开销的计算,仅会在某个依赖项改变时才重新计算;可以使用useCallback把要传递给子组件的函数包裹起来,这样父组件刷新的时候,传递给子组件的函数指向不会发生改变,可以减少子组件的渲染次数;
const handleUseCallback=useCallback(handleClick,[])
<Child handleClick={handleUseCallback} />
(2)useMemo:useMemo的使用和useCallback差不多,只是useCallback返回的是一个函数,useMemo返回值可以是函数、对象等都可以;
两者都可使用:
(1)React.memo:React.memo 功能同React.PureComponent,但React.memo是高阶组件,既可以用在类组件中也可以用在函数组件中;memo还可以接收第二个参数,是一个可定制化的比较函数,其返回值与 shouldComponentUpdate的相反;
(2)使用key:在列表渲染时使用key,这样当组件发生增删改、排序等操作时,diff运算后可以根据key值直接调整DOM顺序,避免不必要的渲染而避免性能的浪费;
(3)不要滥用props:尽量只传需要的数据,避免多余的更新,尽量避免使用{…props};
*/
★★★ useEffect 和 useLayoutEffect 的区别
/*
2、useEffect和useLayout都是副作用hooks,两则非常相似,同样都接收两个参数:
(1)第一个参数为函数,第二个参数为依赖列表,只有依赖更新时才会执行函数;返回一个函数,当页面刷新的或销毁的时候执行return后的代码;
(2)如果不接受第二个参数,那么在第一次渲染完成之后和每次更新渲染页面的时候,都会调用useEffect的回调函数;
useEffect和 useLayout的主要区别就是他们的执行时机不同,在浏览器中js线程与渲染线程是互斥的,当js线程执行时,渲染线程呈挂起状态,只有当js线程空闲时渲染线程才会执行,将生成的 dom绘制。useLayoutEffect在js线程执行完毕即dom更新之后立即执行,而useEffect是在渲染结束后才执行,也就是说 useLayoutEffect比 useEffect先执行。
*/
★★★ hooks 的使用有什么注意事项
/*
(1)只能在React函数式组件或自定义Hook中使用Hook。
(2)不要在循环,条件或嵌套函数中调用Hook,必须始终在React函数的顶层使用Hook。这是因为React需要利用调用顺序来正确更新相应的状态,以及调用相应的钩子函数。一旦在循环或条件分支语句中调用Hook,就容易导致调用顺序的不一致性,从而产生难以预料到的后果。
*/
★★★ 纯函数有什么特点,副作用函数特点
/*
纯函数与外界交换数据只有一个唯一渠道——参数和返回值;函数从函数外部接受的所有输入信息都通过参数传递到该函数内部;函数输出到函数外部的所有信息都通过返回值传递到该函数外部。
纯函数的优点:无状态,线程安全;纯函数相互调用组装起来的函数,还是纯函数;应用程序或者运行环境可以对纯函数的运算结果进行缓存,运算加快速度。
函数副作用是指当调用函数时,除了返回函数值之外,还对主调用函数产生附加的影响。比如调接口、修改全局变量、抛出一个异常或以一个错误终止、打印到终端或读取用户输入、读取或写入一个文件等,所以说副作用是编程中最关键的部分,因为我们需要跟用户、跟数据进行交互。
*/
★★ React 中 refs 干嘛用的?如何创建 refs?
见上
★★★ 在构造函数调用 super
并将 props
作为参数传入的作用是啥?
/*
ES6 中在调用 super()方法之前,子类构造函数无法使用this引用,在react的类组件中也是如此;将 props 参数传递给 super() 调用的主要原因是在子构造函数中能够通过this.props来获取传入的 props。
*/
★★★ 如何 React.createElement ?
见上
★★★ 讲讲什么是 JSX ?
/*
JSX全称为JavaScript XML,是react中的一种语法糖,可以让我们在js代码中脱离字符串直接编写html代码;本身不能被浏览器读取,必须使用@babel/preset-react和webpack等工具将其转换为传统的JS。
主要有以下特点:
(1)类XML语法容易接受,结构清晰;
(2)增强JS语义;
(3)抽象程度高,屏蔽DOM操作,跨平台;
(4)代码模块化;
*/
★★★ 为什么不直接更新 state
呢?
// 如果试图直接更新 state ,则不会重新渲染组件;需要使用setState()方法来更新 state这样组件才会重新渲染;
★★★ React 组件生命周期有哪些不同阶段?React 的生命周期方法有哪些?
见上
★★★ 这三个点(…)在 React 干嘛用的?
// ...是es6语法新出的规范,叫做展开运算符;在react中可以将对象或数组进行展开,让我们操作改变数据结构非常方便。
★★★ React 中的 useState()
是什么?
// useState是一个内置的React Hook,可以让我们在函数组件中像类组件一样使用state并且改变state的值。
★★★ React 中的 StrictMode(严格模式)是什么?
/*
React的StrictMode是一种辅助组件,用<StrictMode />包装组件,可以帮助我们编写更好的react组件,不会渲染出任何可见的ui;仅在开发模式下运行,它们不会影响生产构建,可以做以下检查:
(1)验证内部组件是否遵循某些推荐做法,如果没有,会在控制台给出警告;
(2)验证是否使用的已经废弃的方法,如果有,会在控制台给出警告;
(3)通过识别潜在的风险预防一些副作用。
*/
★★★ 为什么类方法需要绑定到类实例?
// 在 JS 中,this 值会根据当前上下文变化。在 React 类组件方法中,开发人员通常希望 this 引用组件的当前实例,因此有必要将这些方法绑定到实例。通常这是在构造函数中完成的:
★★★★ 什么是 prop drilling,如何避免?
/*
从一个外部组件一层层将prop传递到内部组件很不方便,这个问题就叫做 prop drilling;主要缺点是原本不需要数据的组件变得不必要地复杂,并且难以维护,代码看起来也变得冗余,不优雅;
为了避免prop drilling,一种常用的方法是使用React Context。通过定义提供数据的Provider组件,并允许嵌套的组件通过 Consumer组件或 useContext Hook 使用上下文数据。
*/
★★ 描述 Flux 与 MVC?
/*
传统的 MVC 模式在分离数据(Model)、UI(View和逻辑(Controller)方面工作得很好,但是 MVC 架构经常遇到两个主要问题:
数据流不够清晰——跨视图发生的级联更新常常会导致混乱的事件网络,难于调试。
缺乏数据完整性——模型数据可以在任何地方发生突变,从而在整个UI中产生不可预测的结果。
使用 Flux 模式的复杂用户界面不再遭受级联更新,任何给定的React 组件都能够根据 store 提供的数据重建其状态。Flux 模式还通过限制对共享数据的直接访问来加强数据完整性。
*/
★★★ 这段代码有什么问题吗?
this.setState((prevState, props) => {
return {
streak: prevState.streak + props.count,
}
})
// 没有问题
★★★★ 什么是 React Context?
What
Context提供了一个无需为每层组件手动添加props,就能在组件树间进行数据传递的功能
Why
某些全局属性,通过父子 props 传递太过繁琐,Context 提供了一种组件之间共享此类值的方式,而不必显式的通过组件树逐层传递 props
When
共享那些对于一个组件树而言是全局的数据,例如当前认证的用户、主题或者首选语言等
Where
- Context 应用场景在于很多不同层级的组件访问同样的数据,这样也使得组件的复用性变差。
- 如果你只是想避免层层传递一些属性,组件组合有时候是一个比 Context 更好的方案,也就是直接传递组件
- 所以一个技术方案的选定需要针对不同的场景具体分析,采取合适的方案
How
// ①创建
const ThemeContext = React.createContext('xxx')
// ②注入---提供者 在入口或者你想要注入的父类中,且可以嵌套,里层覆盖外层
return (
<ThemeContext.Provider value="yyy">
{children}
<ThemeContext.Provider>
)
// ③使用---消费者 需要使用共享数据的子类中
// 方式一
static contextType = ThemeContext
// 方式二
Class.contextType = ThemeContext
render() {
let value = this.context
/* 基于这个值进行渲染工作 */
}
// 方式三
return(
<ThemeContext.Consumer>
{ value => /* 基于 context 值进行渲染*/ }
</ThemeContext.Consumer>
)
More
动态 Context—-类似父子组件
// ①创建
const ThemeContext = React.createContext({
value: 'xxx',
changeFunc: () => {} //通过context传递这个函数,让consumers组件更新context
})
// ②注入
return (
<ThemeContext.Provider value="yyy">
<Child changeFunc={this.changeFunc}>
<ThemeContext.Provider>
)
// ③消费
return(
<ThemeContext.Consumer>
{ ({value, changeFunc}) => /* 基于 context 值进行渲染,同时把changeFunc绑定*/ }
</ThemeContext.Consumer>
)
消费多个 Context、注意事项等参考 React 中文网
★★★★★ 什么是 React Fiber?
What
- Fiber 是 React16 中新的协调引擎,它的主要目的是使 Virtual DOM 可以进行增量式渲染,让界面渲染更流畅
- 一种流程控制原语,也称为协程,可以类比 es6 中的 generator 函数;React 渲染的过程可以被中断,可以将控制权交回浏览器,让位给高优先级的任务,浏览器空闲后再恢复渲染。
- 一个执行单元,每次执行完一个“执行单元”,React 就会检查现在还剩多少时间,如果没有时间就将控制权让出去。
- 目标
- 把可中断的工作拆分成小任务
- 对正在做的工作调整优先次序、重做、复用上次(做了一半的)成果
- 在父子任务之间从容切换(yield back and forth),以支持 React 执行过程中的布局刷新
- 支持 render()返回多个元素
- 更好地支持 error boundary
- 特性
- 增量渲染(把渲染任务拆分成块,匀到多帧)
- 更新时能够暂停,终止,复用渲染任务
- 给不同类型的更新赋予优先级
- 并发方面新的基础能力
★★★ 如何在 React 的 Props 上应用验证?
使用 PropTypes 进行类型检查
PropTypes自 React v15.5 起,请使用这个库prop-types
What & Why & When
- 随着应用的不断增长,也是为了使程序设计更加严谨,我们通常需要对数据的类型(值)进行一些必要的验证
- 出于性能方面的考虑,propTypes 仅在开发模式下进行检测,在程序运行时就能检测出错误,不能使用到用户交互提醒用户操作错误等
- 也可以使用Flow或者TypeScript做类型检查,后期建议用 typescript 进行替代更好
Where
- class 组件
- 函数组件
- React.memo高阶组件 可自行扩展
- React.forwardRef组件 可自行扩展
How
我们在组件类下添加一个静态属性 propTypes (属性名不能更改),它的值也是一个对象,用来设置组件中 props 的验证规则,key 是要验证的属性名称,value 是验证规则。
// 类组件
import PropTypes from 'prop-types';
class Greeting extends React.Component {
render() {
return (
<h1>Hello, {this.props.name}</h1>
);
}
}
// 指定 props 的默认值:
Greeting.defaultProps = {
name: 'Stranger'
};
// 类组件在这里做检测
Greeting.propTypes = {
// v15.4 and below
// name: React.PropTypes.string
name: PropTypes.string
};
// 函数组件
unction HelloWorldComponent({ name }) {
return (
<div>Hello, {name}</div>
)
}
// 函数组件在这里做检测
HelloWorldComponent.propTypes = {
name: PropTypes.string
}
export default HelloWorldComponent
More
- 限制单个元素 PropTypes.element
★★ 在 React 中使用构造函数和 getInitialState 有什么区别?
// ES6
class MyComponent extends React.Component {
constructor(props) {
super(props)
this.state = {
/* initial state */
}
}
}
// ES5
var MyComponent = React.createClass({
getInitialState() {
return {
/* initial state */
}
},
})
本质上其实是等价的?- 区别在于 ES6 和 ES5 本身,getInitialState 是搭配 React.createClass 使用的, constructor 是搭配 React.Component 使用的
- 在 React 组件的生命周期中 constructor 先于 getInitialState
★★★ 如何有条件地向 React 组件添加属性?
- 对于某些属性,React 足够智能可以忽略该属性,比如值为 boolean 值属性的值
- 也可以写控制语句管理是否给组件添加属性
★★★★ Hooks 会取代 render props
和高阶组件吗?
- 可以取代,但没必要
- 在 Hook 的渐进策略中也有提到,没有计划从 React 中移除 class,在新的代码中同时使用 Hook 和 class,所以这些方案目前还是可以有勇武之地
What
- 为什么要把这 3 种技术拿过来对比?
都在处理同一个问题,逻辑复用
- 高阶组件 HOC—-不是 React API 的一部分,是基于 React 的组合特性形成的设计模式。
高阶组件是参数为组件,返回值为新组件的函数(将组件转换为另一个组件,纯函数,无副作用)
是指一种在 React 组件之间使用一个值为函数的 prop 共享代码的 简单技术?
React16.8 新增的特性,是一些可以让你在函数组件里“钩入”React state 及生命周期等特性的函数,
Why
虽然 HOC & Render Props 能处理逻辑复用的问题,但是却存在各自的问题。
HOC 存在的问题
- 写法破坏了原来组件的结构,DevTools 中组件会形成“嵌套地狱”
- 不要在 render 方法中使用 HOC 每次调用 render 函数会创建一个新的高阶组件导致该组件及其子组件的状态丢失
- 需要修复静态方法,即拷贝原组件的静态方法到高级组件中
- 如需传递 Ref 则需要通过 React.forwardRef 创建组件
Render Props 存在的问题
- 同样的写法会破坏原来组件的结构,DevTools 中组件会形成“嵌套地狱”
- 与 React.PureComponent 组件使用有冲突
Hook 目前最优雅的实现,React 为共享状态逻辑提供最好的原生途径
- 没有破坏性改动,完全可选,100%向后兼容
- 解决复杂组件,中逻辑状态、副作用和各种生命周期函数中逻辑代码混在一起,难以拆分,甚至形成 bug 的问题
- 处理 class 组件中
When
- 在函数组件中意识到要向其添加一些 state—-useState
- 有副作用的行为时
Where
- 只能在函数最外层调用 Hook,不要在循环、条件判断或者子函数中调用
- 只能在函数组件或者自定义 Hook 中调用 Hook
How
★★★ 如何避免组件的重新渲染?
Answer
当 porps/state 改变时组件会执行 render 函数也就是重新渲染
- class 组件中 使用 shouldComponentUpdate 钩子函数
- PureComponent 默认有避免重新渲染的功能
- 函数组件使用高阶组件 memo 处理
★★★ 什么是纯函数?
Answer
一个不会更改入参,且多次调用下相同的入参始终返回相同的结果
★★★★ 当调用setState
时,React render
是如何工作的?
Answer
调用 setState()
- 检查上下文环境生成更新时间相关参数并判定事件优先级(fiber,currenttime,expirationtime 等…)
- 根据优先级相关参数判断更新模式是 sync(同步更新)或是 batched(批量处理)
- 加入执行更新事件的队列,生成事件队列的链表结构
- 根据链表顺序执行更新
setState 既是同步的,也是异步的。同步异步取决于 setState 运行时的上下文。且 setState 只在合成事件和钩子函数中是“异步”的,在原生 DOM 事件和 setTimeout 中都是同步的
render 如何工作
- React 在 props 或 state 发生改变时,会调用 React 的 render 方法,创建一颗不同的树
- React 需要基于这两颗不同的树之间的差别来判断如何有效的更新 UI
- diff 算法,将两颗树完全比较更新的算法从 O(n),优化成 O(n);
- 同层节点之间相互比较,不会跨节点比较
- 不同类型的节点,产生不同的树结构
- 设置 key 来指定节点在不同的渲染下保持稳定
★★★ 如何避免在 React 重新绑定实例?
Answer
- 将事件处理程序定义为内联箭头函数
- 使用箭头函数来定义方法
- 使用带有 Hooks 的函数组件
★★★ 在 js 原生事件中 onclick 和 jsx 里 onclick 的区别
Answer
js 原生中
- onclick 添加事件处理函数是在全局环境下执行,污染了全局环境,
- 且给很多 dom 元素添加 onclick 事件,影响网页的性能,
- 同时如果动态的从 dom 树种删除了该元素,还要手动注销事件处理器,不然就可能造成内存泄露
jsx 里的 onClick
- 挂载的函数都控制在组件范围内,不会污染全局空间
- jsx 中不是直接使用 onclick,而是采取了事件委托的方式,挂载最顶层 DOM 节点,所有点击事件被这个事件捕获,然后根据具体组件分配给特定函数,性能当然比每个 onClick 都挂载一个事件处理函数要高
- 加上 React 控制了组件的生命周期,在 unmount 的时候自然能够清楚相关的所有事件处理函数,内存泄露不再是一个问题
★★★★ diff 复杂度原理及具体过程画图
Answer
- React 通过制定大胆的 diff 策略,将 O(n3) 复杂度的问题转换成 O(n) 复杂度的问题;
- React 通过分层求异的策略,对 tree diff 进行算法优化;
- React 通过相同类生成相似树形结构,不同类生成不同树形结构的策略,对 component diff 进行算法优化;
- React 通过设置唯一 key 的策略,对 element diff 进行算法优化;
- 建议,在开发组件时,保持稳定的 DOM 结构会有助于性能的提升;
- 建议,在开发过程中,尽量减少类似将最后一个节点移动到列表首部的操作,当节点数量过大或更新操作过于频繁时,在一定程度上会影响 React 的渲染性能。
★★★★ shouldComponentUpdate 的作用是什么?
What
- 不常用的生命周期方法,能影响组件是否重新渲染
- 在更新阶段,当有 new props 或者 调用了 setState()方法,在 render 方法执行前会执行到,默认返回值为 true,如果返回 false 则不刷新组件
Why & When & Where
- 如果你知道在什么情况下组件不需要更新,你可以让其返回值为 false 跳过整个渲染过程
- 次方法仅作为 性能优化方式 而存在,不要企图靠此方法来阻止渲染,
- 大部分情况下,使用 PureComponent 代替手写 shouldComponentUpdate,仅浅层对比
- 不建议在 shoulComponentUpdate 中进行深层或者使用 JSON.stringify(),这样非常影响效率和性能
Answer
- 作为 React 组件中不常用的生命周期函数,能影响组件是否重渲染
- 建议做浅层次的比较,来优化性能,当然这里也可以用 PureComponent 组件代替
- 如果有较深层次的比较则可能会导致更严重的性能问题,因此在这种情况下不要靠手动管理组件的重新渲染来优化性能,要找其他方式
- 比如?
★★★ React 组件间信息传递
Answer
- 1.(父组件)向(子组件)传递信息 : porps 传值
- 2.(父组件)向更深层的(子组件) 进行传递信息 : context
- 3.(子组件)向(父组件)传递信息:callback
- 4.没有任何嵌套关系的组件之间传值(比如:兄弟组件之间传值): 利用共同父组件 context 通信、自定义事件
- 5.利用 react-redux 进行组件之间的状态信息共享 : 组件间状态信息共享:redux、flux、mobx 等
★★★ React 状态管理工具有哪些?redux actionCreator 都有什么?
Answer
- 简单状态管理:组件内部 state、基*于 Context API 封装、
- 复杂状态管理:redux(单项数据流)、mobx(响应式数据流)、RxJS(stream)、dva
- 创建各种 action,包含同步、异步,然后在组件中通过 dispatch 调用
★★★★ 什么是高阶组件、受控组件及非受控组件?都有啥区别
Answer
定义
- 高阶组件 HOC—-不是 React API 的一部分,是基于 React 的组合特性形成的设计模式。
高阶组件是参数为组件,返回值为新组件的函数(将组件转换为另一个组件,纯函数,无副作用)
在表单元素中,state 是唯一数据源,渲染表单的 React 组件控制着用户输入过程中表单发生的操作。被 React 以这种方式控制取值的表单输入元素叫做受控组件
表单数据由 DOM 节点来处理,而不是用 state 来管理数据,一般可以使用 ref 来从 DOM 节点中获取表单数据
区别
- 受控组件和非受控组件是表单中的组件,高阶组件相当于对某个组件注入一些属性方法
- 高阶组件是解决代码复用性问题产生的技术
- 受控组件必须要有一个 value,结合 onChange 来控制这个 value,取值为 event.target.value/event.target.checked
- 非受控组件相当于操作 DOM,一般有个 defaultValue,通过 onBlur 触发响应方法
★★★ vuex 和 redux 的区别?
Answer
Redux
- Redux 是 JavaScript 状态容器,提供可预测化的状态管理。
Redux
- 随着 JS 单页面应用日趋复杂,JS 需要管理比任何时候都要多的 state(服务器响应,缓存数据,本地生成尚未持久化到服务器的数据,UI 状态等)
- 伴随的现象就是 models 和 views 相互影响,你难以弄清楚变化的源头
- 也就是变化和异步让我们的 state 变得一团糟
★★★ Redux 遵循的三个原则是什么?
Answer
单一数据源
- 整个应用的 state 被存储在一棵 object tree 中,并且整个 object tree 只存在于唯一一个 store 中
State 是只读的
- 唯一改变 state 的方法就是触发 action,action 是一个描述已发生事件的普通对象
- 这样确保视图和网络请求不能直接修改 state
使用纯函数来执行修改
- 为了描述 action 如何改变 state tree,你需要编写 reducers
★★★ React 中的 keys 的作用是什么?
Answer
key 是用来帮助 react 识别哪些内容被更改、添加或者删除。key 需要写在用数组渲染出来的元素内部,并且需要赋予其一个稳定的值。稳定在这里很重要,因为如果 key 值发生了变更,react 则会触发 UI 的重渲染。这是一个非常有用的特性。
- key 的唯一性
在相邻的元素间,key 值必须是唯一的,如果出现了相同的 key,同样会抛出一个 Warning,告诉相邻组件间有重复的 key 值。并且只会渲染第一个重复 key 值中的元素,因为 react 会认为后续拥有相同 key 的都是同一个组件。
- key 值不可读
虽然我们在组件上定义了 key,但是在其子组件中,我们并没有办法拿到 key 的值,因为 key 仅仅是给 react 内部使用的。如果我们需要使用到 key 值,可以通过其他方式传入,比如将 key 值赋给 id 等
★★★ redux 中使用 setState 不能立刻获取值,怎么办
Answer
setState 只在合成事件和钩子函数中是异步的,在原生事件和 setTimeout 中都是同步
- ①addeventListener 添加的事件或者 dom 事件中触发
- ②setState 接收的参数还可以是一个函数,在这个函数中可以拿先前的状态,并通过这个函数的返回值得到下一个状态。
this.setState((preState) => {
return {
xxx: preState.xxx + yyy,
}
})
- ③async/await 异步调用处理
★★ 什么是 JSX
Answer
当 Facebook 第一次发布 React 时,他们还引入了一种新的 JS 方言 JSX,将原始 HTML 模板嵌入到 JS 代码中。JSX 代码本身不能被浏览器读取,必须使用 Babel 和 webpack 等工具将其转换为传统的 JS。很多开发人员就能无意识使用 JSX,因为它已经与 React 结合在一直了
- 是一个 JavaScript 的语法扩展
- 具有 JavaScript 的全部功能
- 可以生成 React “元素”
- JSX 也是一个表达式
★★★ React 新老版生命周期函数
Answer
New Version
- 挂载:constructor —> getDerivedStateFromProps —> render —> componentDidMount
- 更新:
- New props、setState() —> getDerivedStateFromProps —> shouldComponentUpdate —> render —> getSnapshotBeforeUpdate —> componentDidUpdate
- forceUpdate() —> getDerivedStateFromProps —> render —> getSnapshotBeforeUpdate —> componentDidUpdate
- 卸载: componentWillUnmount
Old Version*
- 挂载:constructor —> getDerivedStateFromProps —> render —> ComponentDidMount
- 更新:
- New props —> getDerivedStateFromProps —> shouldComponentUpdate —> render —> getSnapshotBeforeUpdate —> componentDidUpdate
- setState() —> shouldComponentUpdate —> render —> getSnapshotBeforeUpdate —> componentDidUpdate
- forceUpdate() —> render —> getSnapshotBeforeUpdate —> componentDidUpdate
- 卸载:componentWillUnmount
★★★★ vue react 都怎么检测数据变化
Answer
- React
React 默认是通过比较引用的方式(diff)进行的,不精确监听数据变化,如果不优化可能导致大量不必要的 VDOM 重新渲染
- 16 之前 componentWillReveiveProps 监听 props 变化
- 16 之后 getDerivedStateFromProps 监听 props
- Vue
- vue 监听变量变化依靠 watch Object.defineProperty,Vue 通过“getter/setter”以及一些函数的劫持,能精确知道数据变化
★★★ React 中怎么让 setState 同步更新?
Answer
- setState 回调,setState,第二个参数是一个回调函数,可实现同步
- 引入 Promise 封装 setState,在调用时我们可以使用 Async/Await 语法来优化代码风格
setStateAsync(state) {
return new Promise((resolve) => {
this.setState(state, resolve)
});
}
- 传入状态计算函数, setState 的第一个参数,
this.setState((prevState, props) => ({
count: prevState.count + 1
}));
- 在 setTimeout 函数中调用 setState
- more?
★★★★ 什么是 immutable?为什么要使用它?
Answer
immutable 是一种持久化数据。一旦被创建就不会被修改。修改 immutable 对象的时候返回新的 immutable。但是原数据不会改变。
在 Rudux 中因为深拷贝对性能的消耗太大了(用到了递归,逐层拷贝每个节点)。
但当你使用 immutable 数据的时候:只会拷贝你改变的节点,从而达到了节省性能。
总结:immutable 的不可变性让纯函数更强大,每次都返回新的 immutable 的特性让程序员可以对其进行链式操作,用起来更方便。因为在 react 中,react 的生命周期中的 setState()之后的 shouldComponentUpdate()阶段默认返回 true,所以会造成本组件和子组件的多余的 render,重新生成 virtual dom,并进行 virtual dom diff,所以解决办法是我们在本组件或者子组件中的 shouldComponentUpdate()函数中比较,当不需要 render 时,不 render。
当 state 中的值是对象时,我们必须使用深拷贝和深比较!
如果不进行深拷贝后再 setState,会造成 this.state 和 nextState 指向同一个引用,所以 shouldComponentUpdate()返回值一定是 false,造成 state 值改了,而组件未渲染(这里不管 shouldComponentUpdate 中使用的是深比较还是浅比较)。所以必须深拷贝。
如果不在 shouldComponentUpdate 中进行深比较,会造成即使 state 中的对象值没有改变,因为是不同的对象,而在 shouldComponentUpdate 返回 true,造成不必要的渲染。
所以只能是深拷贝和深比较。
★★★ 为什么不建议在 componentWillMount 做 AJAX 操作
Answer
- Fiber 原因,React16 之后,采用了 Fiber 架构,只有 componentDidMount 的生命周期函数确定会执行一次,其他像 componentWillMount 可能会执行多次
- render 阶段 可能会被 React 暂停,中止或重启
★★★★ 如何在 React 中构建一个弹出的遮罩层
Answer
//css部分
.mask {
background: rgba(0, 0, 0, 0.4) !important;
z-index: 10;
height: 100vh;
position: fixed;
width: 100vw;
}
.selectMask_box {
background: rgba(0, 0, 0, 0);
transition: all 0.2s linear;
}
//js部分
handleMask = () => {
this.setState({
dateSelected: !this.state.dateSelected,
})
}
;<div
onClick={this.handleMask}
className={`selectMask_box ${this.state.dateSelected ? "mask" : ""} `}>
//这里是待展示的内容,<div>...</div>
//你可以自己设置dataSelected的初始值,同时请注意注意三元运算的顺序。
</div>
★★★★★ React 中的 Context 的使用
Answer
// ①创建
const ThemeContext = React.createContext('xxx')
// ②注入---提供者 在入口或者你想要注入的父类中,且可以嵌套,里层覆盖外层
return (
<ThemeContext.Provider value="yyy">
{children}
<ThemeContext.Provider>
)
// ③使用---消费者 需要使用共享数据的子类中
// 方式一
static contextType = ThemeContext
// 方式二
Class.contextType = ThemeContext
render() {
let value = this.context
/* 基于这个值进行渲染工作 */
}
// 方式三
return(
<ThemeContext.Consumer>
{ value => /* 基于 context 值进行渲染*/ }
</ThemeContext.Consumer>
)
动态 Context—-类似父子组件
// ①创建
const ThemeContext = React.createContext({
value: 'xxx',
changeFunc: () => {} //通过context传递这个函数,让consumers组件更新context
})
// ②注入
return (
<ThemeContext.Provider value="yyy">
<Child changeFunc={this.changeFunc}>
<ThemeContext.Provider>
)
// ③消费
return(
<ThemeContext.Consumer>
{ ({value, changeFunc}) => /* 基于 context 值进行渲染,同时把changeFunc绑定*/ }
</ThemeContext.Consumer>
)
消费多个 Context、注意事项等参考 React 中文网
★★★★ React 路由懒加载的实现
Answer
- 原理
- webpack 代码分割
- React 利用 React.lazy 与 import()实现了渲染时的动态加载
- 利用 Suspense 来处理异步加载资源时页面应该如何显示的问题
- 1.React.lazy
- 通过 lazy() api 来动态 import 需要懒加载的组件
- import 的组件目前只支持 export default 的形式导出
- Suspense 来包裹懒加载的组件进行加载,可以设置 fallback 现实加载中效果
- React.lazy 可以结合 Router 来对模块进行懒加载。
import { BrowserRouter as Router, Route, Switch } from 'react-router-dom';
import { Suspense, lazy } from 'react';
const Home = lazy(() => import('./routes/Home'))
const AnyComponent = lazy(() => import('./routes/AnyComponent'))
...
return (
<Router>
<Suspense fallback={<div>Loading...</div>}>
<Switch>
<Route exact path="/" component={Home}/>
<Route path="/anyManage" component={AnyComponent}/>
...
</Switch>
</Suspense>
</Router>
- 2.react-loadable
react-loadable 是以组件级别来分割代码的,这意味着,我们不仅可以根据路由按需加载,还可以根据组件按需加载,使用方式和路由分割一样,只用修改组件的引入方式即可
// 路由懒加载(异步组件)
import Loadable from 'react-loadable';
//通用过场组件
const LoadingComponent = () => {
return (
<div>loading</div>
)
}
...
export default (loader, loading=LoadingComponent) => {
return Loadable({
loader,
loading
})
}
//Route中调用
import { BrowserRouter, Route } from 'react-router-dom'
const loadable from './loadable';
const AnyComponent = loadable(() => import('./AnyComponent'))
const Routes = () => (
<BrowserRouter>
<Route path="/home" component={AnyComponent}/>
</BrowserRouter>
);
export default Routes;
以下是老版中的方法
- 3.webpack 配置中使用 lazyload-loader
// webpack 配置中
module: {
rules: [
{
test: /.(js|jsx)$/,,
use: [
'babel-loader',
'lazyload-loader'
]
},
// 业务代码中
// 使用lazy! 前缀 代表需要懒加载的Router
import Shop from 'lazy!./src/view/Shop';
// Router 正常使用
<Route path="/shop" component={Shop} />
- 4.import() webpack v2+
符合 ECMAScript 提议的 import()语法,该提案与普通 import 语句或 require 函数的类似,但返回一个 Promise 对象
function component() {
return import(/* webpackChunkName: "lodash" */ "lodash")
.then((_) => {
var element = document.createElement("div")
element.innerHTML = _.join(["Hello", "webpack"], " ")
return element
})
.catch((error) => "An error occurred while loading the component")
}
// 或者使用async
async function getComponent() {
var element = document.createElement("div")
const _ = await import(/* webpackChunkName: "lodash" */ "lodash")
element.innerHTML = _.join(["Hello", "webpack"], " ")
return element
}
- 5.requre.ensure webpack v1 v2
require.ensure([], function(require){
var list = require('./list');
list.show();
,'list');
<!-- Router -->
const Foo = require.ensure([], () => {
require("Foo");
}, err => {
console.error("We failed to load chunk: " + err);
}, "chunk-name");
//react-router2 or 3
<Route path="/foo" getComponent={Foo} />
★★★★ React-router-dom 内部是怎么样实现的,怎么做路由守卫?
Answer
内部实现
- 总
react-router-dom 利用了 Context API,通过上下文对象将当前路由信息对象注入到 Router 组件中,所以 Router 组件中 render() 渲染的内容就是 ContextAPI 提供的 Provider 组件,然后接收 Router 组件中的当前路由信息对象。
这样 Router 组件下的所有组件都能通过上下文拿到当前路由信息对象,即其中的Switch 、 Route 、 Link 、Redirect 等组件都可以拿到当前路由信息对象,然后通过改变当前路由信息来实现动态切换 Route 组件的渲染。
- 分
- RouterContext:react-router 使用 context 实现跨组件间数据传递,所以 react-router 定义了一个 routerContext 作为数据源,
- Router:BrowserRouter 和 HashRouter 将当前路由注入到上下文中,同时路由信息包含 location、match、history
- Route:路由规则,获取 RouterContext 的信息(location 对象),获取 path 和 component 属性,判断 path 和当前的 location 是否匹配,如果匹配,则渲染 component,否则返回 null,不渲染任何内容
- Switch:遍历所有子元素(Route),判断 Route 的 path 和 location 是否匹配,如果匹配,则渲染,否则不渲染
- Redireact:未能配则重定向到指定页面
- Link/NavLink: Link 组件本质就是 a 标签,它修改了 a 标签的默认行为,当点击 Link 时,会导航到对应的路由,导致 locaiton 对象的改变,出发组件的更新
- withRouter:对传入的组件进行加强,功能就是获取 routerContext 上面的信息,然后作为 props 传给需要加强的组件
怎么做路由守卫
- 路由里设置 meta 元字符实现路由拦截
- React Router 4.0 之前也像 vue 中一样有个钩子函数 onEnter 可实现
- ReactRouter 4.0 开始自己实现如下
// routerMap.js中
import Index from './page/index'
export default [
{ path:'/', name: 'App', component:Index, auth: true },
...
]
//入口文件 app.js中
import { BrowserRouter as Router, Switch } from "react-router-dom";
import FrontendAuth from "./FrontendAuth";
import routerMap from "./routerMap";
...
return (
<Router>
<div>
<Switch>
<FrontendAuth routerConfig={routerMap} />
</Switch>
</div>
</Router>
)
// 高阶组件FrontendAuth 处理路由跳转,即路由守卫功能
//FrontendAuth.js
import React, { Component } from "react";
import { Route, Redirect } from "react-router-dom";
class FrontendAuth extends Component {
// eslint-disable-next-line no-useless-constructor
constructor(props) {
super(props);
}
render() {
const { routerConfig, location } = this.props;
const { pathname } = location;
const isLogin = sessionStorage.getItem("username");
console.log(pathname, isLogin);
console.log(location);
// 如果该路由不用进行权限校验,登录状态下登陆页除外
// 因为登陆后,无法跳转到登陆页
// 这部分代码,是为了在非登陆状态下,访问不需要权限校验的路由
const targetRouterConfig = routerConfig.find(
(item) => item.path === pathname
);
console.log(targetRouterConfig);
if (targetRouterConfig && !targetRouterConfig.auth && !isLogin) {
const { component } = targetRouterConfig;
return <Route exact path={pathname} component={component} />;
}
if (isLogin) {
// 如果是登陆状态,想要跳转到登陆,重定向到主页
if (pathname === "/login") {
return <Redirect to="/" />;
} else {
// 如果路由合法,就跳转到相应的路由
if (targetRouterConfig) {
return (
<Route path={pathname} component={targetRouterConfig.component} />
);
} else {
// 如果路由不合法,重定向到 404 页面
return <Redirect to="/404" />;
}
}
} else {
// 非登陆状态下,当路由合法时且需要权限校验时,跳转到登陆页面,要求登陆
if (targetRouterConfig && targetRouterConfig.auth) {
return <Redirect to="/login" />;
} else {
// 非登陆状态下,路由不合法时,重定向至 404
return <Redirect to="/404" />;
}
}
}
}
export default FrontendAuth;
- 总结一下,实现路由守卫需要考虑到以下的问题:
- 未登录情况下,访问不需要权限校验的合法页面:允许访问
- 未登录情况下,访问需要权限校验的页面:禁止访问,跳转至登陆页
- 未登录情况下,访问所有的非法页面:禁止访问,跳转至 404
- 登陆情况下,访问登陆页面:禁止访问,跳转至主页
- 登陆情况下,访问除登陆页以外的合法页面:允许访问
- 登陆情况下,访问所有的非法页面:禁止访问,跳转至 404
★★★★ redux 中 sages 和 thunk 中间件的区别,优缺点
Answer
- 区别
- redux-thunk 异步采取 async/await redux-saga 采取 generate 函数
- 优缺点
- redux-thunk
优点: 库小,代码就几行
缺点:代码臃肿,reducer 不再是纯粹函数,直接返回对象,违背了当初的设计原则;action 的形式不统一,异步操作太为分散,分散在了各个 action 中
- redux-saga
优点: 将异步与 reducer 区分开了,更加优雅,适合大量 APi 请求,而且每个请求之间存在复杂的以来关系
缺点:学习曲线比较陡,理解 async await;而且库也比较大,即使发布的最小也有 25kb,gzip 压缩后也有 7KB,React 压缩后才 45kb
★★ 为什么说 React 是 view(视图层)
Answer
- react, 是 Facebook 推出的一个用来构建用户界面的 JavaScript 库. React 主要用于构建 UI
- React 被认为是视图层的框架是因为它是基于组件的,一切都是组件,而组件就是渲染页面的基础。不论组件中包含的 jsx,methods,state,props,都是属于组件内部的
- View(视图)是应用程序中处理数据显示的部分。视图层主要包括二个部分:1.视图层显示及交互逻辑;2.视图层的数据结构 ViewObj, 包括 React 中的 props 和 stats;
★★★ 怎么用 useEffect 模拟生命周期函数?
Answer
- ① 默认情况下,它在第一次渲染之后和每次更新之后都会执行,无需清除的 effect
// 在函数式组件中 在 return之前
// Similar to componentDidMount and componentDidUpdate:
useEffect(() => {
// Update the document title using the browser API
document.title = `You clicked ${count} times`
})
- ② 需要清除的 effect:React 会在组件卸载的时候执行清除操作
useEffect(() => {
function handleStatusChange(status) {
setIsOnline(status.isOnline)
}
ChatAPI.subscribeToFriendStatus(props.friend.id, handleStatusChange)
// Specify how to clean up after this effect:
return function cleanup() {
ChatAPI.unsubscribeFromFriendStatus(props.friend.id, handleStatusChange)
}
})
- ③ 使用多个 effect 实现关注点的分离
把类组件中的分散在多个生命周期中的同一件事件的处理,合并到同一个 effect 中处理
- ④ 通过跳过 effect 进行性能优化
useEffect(() => {
document.title = `You clicked ${count} times`
}, [count]) // 仅在 count 更改时更新
★★★ useCallback 是干什么的?使用 useCallback 有什么好处?
Answer
把内联回调函数及依赖项数组作为参数传入 useCallback,它将返回该回调函数的 memoized(缓存)版本,该回调函数仅在某个依赖项改变时才会更新
好处
当你把回调函数传递给经过优化的并使用引用相等性去避免非必要渲染(例如 shouldComponentUpdate)的子组件时,它将非常有用
★★★ 能简单说一下 redux-sage 的使用流程吗?
Answer
redux-saga 是一个用于管理 Redux 应用异步操作的中间件(又称异步 action)。 redux-saga 通过创建 Sagas 将所有的异步操作逻辑收集在一个地方集中处理,可以用来代替 redux-thunk 中间件。
- Reducers 负责处理 action 的 state 更新
- Sagas 负责协调那些复杂或异步的操作
- ①connet to the store:本质是管理 Redux 应用异步操作的中间件
import { createStore, applyMiddleware } from "redux"
import createSagaMiddleware from "redux-saga"
import reducer from "./reducers"
import mySaga from "./sagas"
// Create the saga middleware
const sagaMiddleware = createSagaMiddleware()
// Mount it on the Store
const store = createStore(reducer, applyMiddleware(sagaMiddleware))
// Then run the saga
sagaMiddleware.run(mySaga)
// Render the application
- ②initiate a side effect:初始化副作用
import { call, put, takeEvery, takeLatest } from "redux-saga/effects"
import Api from "..."
// Worker saga will be fired on USER_FETCH_REQUESTED actions
function* fetchUser(action) {
try {
const user = yield call(Api.fetchUser, action.payload.userId)
yield put({ type: "USER_FETCH_SUCCEEDED", user: user })
} catch (e) {
yield put({ type: "USER_FETCH_FAILED", message: e.message })
}
}
// Starts fetchUser on each dispatched USER_FETCH_REQUESTED action
// Allows concurrent fetches of user
function* mySaga() {
yield takeEvery("USER_FETCH_REQUESTED", fetchUser)
}
- ③dispath an action:组件中使用
class UserComponent extends React.Component {
...
onSomeButtonClicked() {
const { userId, dispatch } = this.props
dispatch({type: 'USER_FETCH_REQUESTED', payload: {userId}})
}
...
}
- ④more:takeEvery、takeLatest、take、put、call、fork、select
★★★★ React 复用组件的状态和增强功能的方法
Answer
- ①render props 模式
- 创建 Mouse 组件,在组件中提供复用的状态逻辑代码
- 将要复用的状态作为 props.render(state) 方法的参数,暴露到组件外部
- 使用 props.render() 的返回值作为要渲染的内容
// 子组件
class Mouse extends React.Component {
// mouse本组件的数据
state = {
x: 0,
y: 0,
}
// mouse本组件的方法
handleMouse = (e) => {
this.setState({
x: e.clientX,
y: e.clientY,
})
}
componentDidMount() {
window.addEventListener("mousemove", this.handleMouse)
}
render() {
// 在这里用props接收从父传过来的render函数,再把state数据作为实参传递出去
// 其实渲染的就是从父传过来的UI结构,只是公用了Mouse组件的数据和方法
return this.props.render(this.state)
}
}
// 父组件
class App extends React.Component {
render() {
return (
<div>
<h1>app组件:{this.props.name}</h1>
{/* 在使用mouse组件时,给mouse传递一个值(父传子),
只不过这里的props是函数,这个函数将要用形参接受从mouse组件传递过来的实参(state数据) */}
<Mouse
render={(mouse) => {
return (
<div>
<h5>
我用的是Mouse的state和方法:X坐标{mouse.x}-Y坐标{mouse.y}
</h5>
</div>
)
}}
/>
</div>
)
}
}
- ② 高阶组件 HOC
const EnhancedComponent = withHOC(WrappedComponent)
// 高阶组件内部创建的类组件:
class Mouse extends React.Component {
render() {
return <WrappedComponent {...this.state} />
}
}
- ③hooks:自定义 hook
// 自定义hook
function useFriendStatus(friendID) {
const [isOnline, setIsOnline] = useState(null)
// ...
return isOnline
}
// 使用
function FriendStatus(props) {
const isOnline = useFriendStatus(props.friend.id)
if (isOnline === null) {
return "Loading..."
}
return isOnline ? "Online" : "Offline"
}
★★★ redux 和 mobx 的区别
Answer
- ①Redux 的编程范式是函数式的而 Mobx 是面向对象的
- ② 因此数据上来说 Redux 理想的是 immutable 的,每次都返回一个新的数据,而 Mobx 从始至终都是一份引用。因此 Redux 是支持数据回溯的
- ③ 然而和 Redux 相比,使用 Mobx 的组件可以做到精确更新,这一点得益于 Mobx 的 observable;对应的,Redux 是用 dispatch 进行广播,通过 Provider 和 connect 来比对前后差别控制更新粒度,有时需要自己写 SCU;Mobx 更加精细一点
- ④Mobx-react vs React-rdux:
redux,采取 Provider 和 connect 方式,mobx 采取 Provider 和 inject、observer
★★★ react 中如何实现命名插槽
Answer
import React, { Component } from "react"
import ReactDOM from "react-dom"
class ParentCom extends React.Component {
constructor(props) {
super(props)
console.log(props)
}
render() {
return (
<div>
<h1>组件插槽</h1>
{this.props.children}
<ChildCom>
<h1 data-position="header">这是放在头部的内容</h1>
<h1 data-position="main">这是放在主体的内容</h1>
<h1 data-position="footer">这是放在尾部的内容</h1>
</ChildCom>
</div>
)
}
}
class ChildCom extends React.Component {
render() {
console.log(this.props)
let headerCom, mainCom, footerCom
this.props.children.forEach((item, index) => {
if (item.props["data-position"] === "header") {
headerCom = item
} else if (item.props["data-position"] === "main") {
mainCom = item
} else {
footerCom = item
}
})
return (
<div>
<div className="header">{headerCom}</div>
<div className="main">{mainCom}</div>
<div className="footer">{footerCom}</div>
</div>
)
}
}
ReactDOM.render(
<ParentCom>
<h2>子组件1</h2>
<h2>子组件2</h2>
<h2>子组件3</h2>
</ParentCom>,
document.getElementById("root")
)
★★★ 简单说一下,如何在 react 中实现瀑布流加载?(左右两列的一个商品长列表)
Answer
根据红线,将数据分为两部分,然后根据两边的高度(哪边少往那边加内容)去渲染两个盒子,然后达到一个瀑布流的效果
import React, { Component, Fragment } from "react"
import { connect } from "react-redux"
import Axios from "_axios@0.19.0@axios"
class Waterfall extends Component {
constructor(props) {
super(props)
this.state = {
data: [], //整体的数据
leftData: [], //左边的数据
rightData: [], //右边的数据
}
}
getHW(data) {
let heightDate = [0, 0] //接收累计高度的容器数组
let rightData = [] //渲染右侧盒子的数组
let leftData = [] //渲染左侧盒子的数组
data.forEach((item) => {
let height = item.src.replace("http://dummyimage.com/", "").substr(0, 7).split("x")[1] * 1 //对url地址进行一个截取,拿到高度
let minNum = Math.min.apply(null, heightDate) // 从heighetData筛选最小项
let minIndex = heightDate.indexOf(minNum) // 获取 最小项的小标 准备开始进行累加
heightDate[minIndex] = heightDate[minIndex] + height //从 heightData 中找到最小的项后进行累加,
if (minIndex === 0) {
//[0]加到left [1]加到 right
leftData.push(item)
} else {
rightData.push(item)
}
})
this.setState({ leftData, rightData }) //重新set state
}
render() {
let { leftData, rightData } = this.state
console.log(leftData, rightData)
return (
<Fragment>
<div className="left">
{leftData &&
leftData.map((item, index) => {
return <img src={item.src} alt={index} key={index} />
})}
</div>
<div className="right">
{rightData &&
rightData.map((item, index) => {
return <img src={item.src} alt={index} key={index} />
})}
</div>
</Fragment>
)
}
componentDidMount() {
Axios.get("/api/data").then((res) => {
this.props.dispatch({
type: "SET_DATA",
data: res.data.data,
})
this.getHW(this.props.data) //调用
})
}
}
export default connect((state) => {
return {
data: state.data,
}
})(Waterfall)