React
React 和 Vue 的异同
共同点:
- 都将注意力集中保持在核心库,而将其他功能如路由和全局状态管理交给相关的库;
- 都有自己的构建工具,能让你得到一个根据最佳实践设置的项目模板;
- 都使用了Virtual DOM(虚拟DOM)提高重绘性能;
- 都有props的概念,允许组件间的数据传递;
- 都鼓励组件化应用,将应用分拆成一个个功能明确的模块,提高复用性。
不同之处:
(1) 数据流向不同:Vue默认是双向数据流,而React一直提倡单向数据流
(2) 虚拟DOM:Vue2.x开始引入"Virtual DOM",消除了和React在这方面的差异,但是在具体的细节还是有各自的特点。
- Vue宣称可以更快地计算出Virtual DOM的差异,这是由于它在渲染过程中,会跟踪每一个组件的依赖关系,不需要重新渲染整个组件树。
- 对于React而言,每当应用的状态被改变时,全部子组件都会重新渲染。当然,这可以通过 PureComponent/shouldComponentUpdate这个生命周期方法来进行控制,但Vue将此视为默认的优化。
(3) 组件化:React与Vue最大的不同是模板的编写。
- Vue鼓励写近似常规HTML的模板。写起来很接近标准 HTML元素,只是多了一些属性。
- React推荐你所有的模板通用JavaScript的语法扩展——JSX书写。
具体来讲:React中render函数是支持闭包特性的,所以import的组件在render中可以直接调用。但是在Vue中,由于模板中使用的数据都必须挂在 this 上进行一次中转,所以 import 一个组件完了之后,还需要在 components 中再声明下。
(4) 监听数据变化的实现原理不同
- Vue 通过 getter/setter 以及一些函数的劫持,能精确知道数据变化,不需要特别的优化就能达到很好的性能
- React 默认是通过比较引用的方式进行的,如果不优化(PureComponent/shouldComponentUpdate)可能导致大量不必要的vDOM的重新渲染。这是因为 Vue 使用的是可变数据,而React更强调数据的不可变。
(5) 高阶组件:react可以通过高阶组件(HOC)来扩展,而Vue需要通过mixins来扩展。
高阶组件就是高阶函数,而React的组件本身就是纯粹的函数,所以高阶函数对React来说易如反掌。相反Vue.js使用HTML模板创建视图组件,这时模板无法有效的编译,因此Vue不能采用HOC来实现。
(6) 构建工具:Vue=>vue-cli、React=>Create React App
(7) 跨平台:React => React Native;Vue => Weex
(8) 维护性:Vue 是完整一套由官方维护的框架,核心库主要有由尤雨溪大神独自维护,而React 是FaceBook维护,很多库由社区维护。
React基础
React 是用于构建用户界面的 JavaScript 库,提供了 UI 层面的解决方案,遵循组件设计模式、声明式编程范式和函数式编程概念,以使前端应用程序更高效,使用虚拟DOM来有效地操作 DOM,遵循从高阶组件到低阶组件的单向数据流,帮助我们将界面成了各个独立的小块,每一个块就是组件,这些组件之间可以组合、嵌套,构成整体页面.
React理解:React 是一个网页UI框架,通过组件化的方式解决视图层开发复用的问题,本质是一个组件化框架。
它的核心设计思路有三点:
- 声明式:优势在于直观与组合
- 组件化:优势在于视图的拆分与模块复用,可以更容易做到高内聚低耦合
- 通用性:通用性在于一次学习,随处编写
特性:JSX语法、单向数据绑定、虚拟DOM、声明式编程、Component(组件化)
优势:
- 高效灵活
- 声明式的设计,简单使用
- 组件式开发,提高代码复用率
- 单向响应的数据流会比双向绑定的更安全,速度更快
React 事件机制
React 并不是将click事件绑定到了div的真实DOM上,而是在document处监听了所有的事件,当事件发生并且冒泡到document处的时候,React将事件内容封装并交由真正的处理函数运行。这样的方式不仅仅减少了内存的消耗,还能在组件挂在销毁时统一订阅和移除事件。
React 组件的 state 和 props
props:是一个从外部传进组件的参数,主要作为就是从父组件向子组件传递数据,它具有可读性和不变性,只能通过外部组件主动传入新的props来重新渲染子组件,否则子组件的props以及展现形式不会改变。
state:主要作用是用于组件保存、控制以及修改自己的状态,它只能在constructor中初始化,它算是组件的私有属性,不可通过外部访问和修改,只能通过组件内部的this.setState来修改,修改state属性会导致组件的重新渲染。
区别
- props 是传递给组件的(类似于函数的形参),而state 是在组件内被组件自己管理的(类似于在一个函数内声明的变量)。
- props 是不可修改的,所有 React 组件都必须像纯函数一样保护它们的 props 不被更改。
- state 是在组件中创建的,一般在 constructor中初始化 state。state 是多变的、可以修改,每次setState都异步更新的。
React 中 setState 的第二个参数作用
setState 的第二个参数是一个可选的回调函数。这个回调函数将在组件重新渲染后执行。等价于在 componentDidUpdate 生命周期内执行。通常建议使用 componentDidUpdate 来代替此方式。在这个回调函数中你可以拿到更新后 state 的值
纯函数
特点:
- 给定相同的输入,总是返回相同的输出。
- 过程没有副作用。
- 不依赖外部状态。
React.Fragment
React Fragment 是 React 中的一个特性,它允许你对一组子元素进行分组,而无需向 DOM 添加额外的节点,从而允许你从 React 组件中返回多个元素。
组件基础
组件本质上就是类和函数,但是与常规的类和函数不同的是,组件承载了渲染视图的 UI 和更新视图的 setState 、 useState 等方法。
React高阶组件
官方解释:高阶组件(HOC)是 React 中用于复用组件逻辑的一种高级技巧。HOC 自身不是 React API 的一部分,它是一种基于 React 的组合特性而形成的设计模式。
高阶组件(HOC)就是一个函数,且该函数接受一个组件作为参数,并返回一个新的组件,它只是一种组件的设计模式,这种设计模式是由react自身的组合性质必然产生的。我们将它们称为纯组件,因为它们可以接受任何动态提供的子组件,但它们不会修改或复制其输入组件中的任何行为。
高阶组件运用了装饰模式,,装饰模式的特点是不需要改变被装饰对象本身,而只是在外面套一个外壳接口。
优点:逻辑服用、不影响被包裹组件的内部逻辑。
缺点:hoc传递给被包裹组件的props容易和被包裹后的组件重名,进而被覆盖
适用场景
- 代码复用,逻辑抽象
- 渲染劫持
- State 抽象和更改
- Props 更改
有状态组件和无状态组件
有状态组件: 是一个class类,继承componet(用于需要一些状态去存储和修改数据)
无状态组件: 是一个es6写的箭头函数函数,并不继承 componet(用于一些简单的逻辑,比如,父组件向子组件传属性值)
区别:
- 最大的区别是无状态组件,无法使用state,因为state是继承 componet
- 无状态组件,没有生命周期函数,生命周期函数是基于state的
受控组件 和 非受控组件
受控组件:是 React 控制中的组件,并且是表单数据真实的唯一来源。
非受控组件:由 DOM 处理表单数据的地方,而不是在 React 组件中。
生命周期
React的生命周期
三个阶段:
- 挂载阶段(Mount):组件第一次在DOM树中被渲染的过程;
- 更新过程(Update):组件状态发生变化,重新更新渲染的过程;
- 卸载过程(Unmount):组件从DOM树中被移除的过程;
React常见生命周期的过程大致如下:
- 挂载阶段,首先执行 constructor 构造方法,来创建组件
- 创建完成后会执行 render 方法,该方法会返回需要渲染的内容
- 随后,React 会将需要渲染的内容挂载到 DOM 树上
- 挂载完成之后就会执行 componentDidMount 生命周期函数
- 如果我们给组件创建一个 props(用于组件通信)、调用 setState(更改state中的数据)、调用 forceUpdate(强制更新组件)时,都会重新调用render函数
- render 函数重新执行之后,就会重新进行DOM树的挂载
- 挂载完成之后就会执行 componentDidUpdate 生命周期函数
- 当移除组件时,就会执行 componentWillUnmount 生命周期函数
组件挂载阶段
挂载阶段组件被创建,然后组件实例插入到 DOM 中,完成组件的第一次渲染,该过程只会发生一次,在此阶段会依次调用以下这些方法:
- constructor():在 React 组件挂载之前,会调用它的构造函数。
- getDerivedStateFromProps:在调用 render 方法之前调用,并且在初始挂载及后续更新时都会被调用
- render:class 组件中唯一必须实现的方法
- componentDidMount:在组件挂在后(插入 DOM 树中)立即调用
constructor
特点:
- React.Component 子类实现构造函数时,应在其他语句之前前调用
super(props)。 - 不要调用 setState() 方法。如果组件需要使用内部 state,请直接在构造函数中为 this.state 赋值初始 state
组件的构造函数,第一个被执行,若没有显式定义它,会有一个默认的构造函数,但是若显式定义了构造函数,必须在构造函数中执行 super(props),否则无法在构造函数中拿到this。
如果不初始化 state 或不进行方法绑定,则不需要为 React 组件实现构造函数Constructor。
constructor 中通常只做两件事:
- 初始化组件的 state
- 给事件处理方法绑定 this
render
render 是 React 中最核心的方法,一个组件中必须要有这个方法,它会根据状态 state 和属性 props 渲染组件。
这个函数只做一件事,就是返回需要渲染的内容,所以不要在这个函数内做其他业务逻辑,通常调用该方法会返回以下类型中一个:
- React 元素:这里包括原生的 DOM 以及 React 组件;
- 数组和 Fragment(片段) :可以返回多个元素;
- Portals(插槽) :可以将子元素渲染到不同的 DOM 子树种;
- 字符串和数字:被渲染成 DOM 中的 text 节点;
- 布尔值或 null:不渲染任何内容。
componentDidMount()
componentDidMount()会在组件挂载后(插入 DOM 树中)立即调用。
该阶段通常进行以下操作:
- 执行依赖于DOM的操作;
- 发送网络请求;(官方建议)
- 添加订阅消息(会在componentWillUnmount取消订阅);
注意:如果在 componentDidMount 中调用 setState ,就会触发一次额外的渲染,多调用了一次 render 函数,由于它是在浏览器刷新屏幕前执行的,所以用户对此是没有感知的,但是应当避免这样使用,这样会带来一定的性能问题,尽量是在 constructor 中初始化 state 对象。
组件更新阶段
当组件的 props 改变了,或组件内部调用了setState / forceUpdate,会触发更新重新渲染,这个过程可能会发生多次,调用顺序如下:
- getDerivedStateFromProps
- shouldComponentUpdate
- render
- getSnapshotBeforeUpdate
- componentDidUpdate
getDerivedStateFromProps()
getDerivedStateFromProps() 会在调用 render 方法之前调用,即在渲染 DOM 元素之前会调用,并且在初始挂载及后续更新时都会被调用。该方法返回一个对象用于更新 state,如果返回 null 则不更新任何内容。
目的:让组件在 props 变化时更新 state。
shouldComponentUpdate()
shouldComponentUpdate() 方法会返回一个布尔值,指定 React 是否应该继续渲染,默认值是 true, 即 state 每次发生变化组件都会重新渲染。
它的返回值用于判断 React 组件的输出是否受当前 state 或 props 更改的影响,当 props 或 state 发生变化时,shouldComponentUpdate() 会在渲染执行之前被调用。
getSnapshotBeforeUpdate()
getSnapshotBeforeUpdate() 方法在最近一次渲染输出(提交到 DOM 节点)之前调用。
在 getSnapshotBeforeUpdate() 方法中,可以访问更新前的 props 和 state。
它需要与 componentDidUpdate() 方法一起使用,否则会出现错误
组件卸载阶段
componentWillUnmount() 方法在组件卸载及销毁之前直接调用。
该方法中不应调用 setState(),因为该组件将永远不会重新渲染。组件实例卸载后,将永远不会再挂载它。
props 相关
componentWillReceiveProps:
props发生变化时执行componentWillReceiveProps
特点:
- 组件初次渲染时不会执行componentWillReceiveProps;
- 当 props 发生变化时执行componentWillReceiveProps;
- 在这个函数里面,旧的属性仍可以通过this.props来获取;
- 此函数可以作为 react 在 prop 传入之后, render() 渲染之前更新 state 的机会。即可以根据属性的变化,通过调用this.setState()来更新你的组件状态,在该函数中调用 this.setState() 将不会引起第二次渲染。
- 也可在此函数内根据需要调用自己的自定义函数,来对prop的改变做出一些响应。
注意:当父组件向子组件传递引用类型(或复合类型,比如对象、数组等)的属性时,要注意打印的this.props和nextProps的内容是一致的,因为引用类型在内存中只有一份,传值时是浅拷贝
组件通信
React的5种主流通信方式:
- props 和 callback 方式
- ref 方式。
- React-redux 或 React-mobx 状态管理方式。
- context 上下文方式。
- event bus 事件总线
组件间常见的通信情况:父组件向子组件通信、子组件向父组件通信、跨级组件通信、非嵌套关系的组件通信
组件通信的方式有哪些
- 父组件向子组件通讯:传 props 的⽅式
- 子组件向父组件通讯:props+回调的方式,父组件向⼦组件传递props进行通讯,此props为作⽤域为父组件⾃身的函数,子组件调用该函数,将子组件想要传递的信息,作为参数,传递到父组件的作⽤域中
- 兄弟组件通信:找到这两个兄弟节点共同的父节点,结合上面两种方式由父节点转发信息进行通信
- 跨层级通信:Context 设计目的是为了共享那些对于⼀个组件树而言是“全局”的数据,例如当前认证的⽤户、主题或首选语⾔,对于跨越多层的全局数据通过 Context 通信再适合不过
- 发布订阅模式: 发布者发布事件,订阅者监听事件并做出反应,可以通过引⼊event模块进⾏通信
- 全局状态管理工具: 借助Redux或者Mobx等全局状态管理⼯具进行通信,这种工具会维护⼀个全局状态中⼼Store,并根据不同的事件产⽣新的状态
跨级组件的通信方式
父组件向子组件的子组件通信,向更深层子组件通信:
- 使用props,利用中间组件层层传递,但是如果父组件结构较深,那么中间每一层组件都要去传递props,增加了复杂度,并且这些props并不是中间组件自己需要的。
- 使用context,context相当于一个大容器,可以把要通信的内容放在这个容器中,这样不管嵌套多深,都可以随意取用,对于跨越多层的全局数据可以使用context实现。
非嵌套关系组件的通信方式
即没有任何包含关系的组件,包括兄弟组件以及不在同一个父级中的非兄弟组件。
- 自定义事件通信(发布订阅模式)
- 通过redux等进行全局状态管理
- 如果是兄弟组件通信,可以找到这两个兄弟节点共同的父节点, 结合父子间通信方式进行通信。
如何解决 props 层级过深的问题
- 使用Context API:提供一种组件之间的状态共享,而不必通过显式组件树逐层传递props;
- 使用Redux等状态库。
路由
React-Router
实现思想:
- 基于
history库来实现上述不同的客户端路由实现思想,并且能够保存历史记录等,磨平浏览器差异,上层无感知 - 通过维护的列表,在每次 URL 发生变化的回收,通过配置的 路由路径,匹配到对应的 Component,并且 render
如何配置实现路由切换:
- 使用
<Route>组件 - 结合使用
<Switch>组件和<Route>组件 - 使用
<Link>、 <NavLink>、<Redirect>组件
怎么设置重定向:使用<Redirect>组件实现路由的重定向:
react-router 里的 Link 标签和 a 标签的区别:
<a>标签就是普通的超链接,用于从当前页面跳转到href指向的另一 个页面(非锚点情况);
<Link>是 react-router 里实现路由跳转的链接,一般配合<Route> 使用,react-router 接管了其默认的链接跳转行为,区别于传统的页面跳转,<Link> 的“跳转”行为只会触发相匹配的<Route>对应的页面内容更新,而不会刷新整个页面。
<Link>做了3件事情:
- 有onclick那就执行onclick
- click的时候阻止a标签默认事件
- 根据跳转href(即是to),用history (web前端路由两种方式之一,history & hash)跳转,此时只是链接变了,并没有刷新页面。
获取URL的参数
- get传值
- 动态路由传值
- 通过query或state传值
获取历史对象:
- 如果React >= 16.8 时可以使用 React Router中提供的Hooks
- 使用this.props.history获取历史对象
路由有几种模式:支持使用 hash(对应 HashRouter)和 browser(对应 BrowserRouter) 两种路由规则, react-router-dom 提供了 BrowserRouter 和 HashRouter 两个组件来实现应用的 UI 和 URL 同步
- BrowserRouter:使用 HTML5 提供的 history API(pushState、replaceState 和 popstate 事件)来保持 UI 和 URL 的同步。
- HashRouter:使用 URL 的 hash 部分(即 window.location.hash)来保持 UI 和 URL 的同步。
React-Router 4 的 Switch有什么用:Switch 通常被用来包裹 Route,用于渲染与路径匹配的第一个子 <Route> 或 <Redirect>,它里面不能放其他元素。
Redux
为什么需要Redux
-
JS开发的应用程序需要管理的状态越来越多,越来越复杂,这些状态包括服务器返回的数据、缓存数据、用户操作产生的数据等等,也包括一些UI的状态,比如某些元素是否被选中,是否显示加载动效,当前分页;
-
管理不断变化的state是非常困难的:
-
React是在视图层帮助我们解决了DOM的渲染过程,但是State依然是留给我们自己来管理:
-
Redux就是一个帮助我们管理State的容器:Redux是JavaScript的状态容器,提供了可预测的状态管理;
-
Redux除了和React一起使用之外,它也可以和其他界面库一起来使用(比如Vue),并且它非常小(包括依赖在内,只有2kb)
概念:Redux是专门用于集中式管理状态的javascript库,它基于flux,并不是React的插件库。 Redux简化了React中的单向数据流。 Redux将状态管理完全从React中抽象出来。
主要解决的问题:单纯的Redux只是一个状态机,是没有UI呈现的,react-redux作用是将Redux的状态机和React的UI呈现绑定在一起,当你dispatch action改变state的时候,会自动更新页面。
Redux的核心理念
-
Store
-
action:Redux要求我们通过action来更新数据: 所有数据的变化,必须通过派发(dispatch)action来更新; action是一个普通的JavaScript对象,用来描述这次更新的type和content;
- reducer:reducer是一个纯函数; reducer做的事情就是将传入的state和action结合起来生成一个新的state;
Redux三大原则:
- 单一数据源
- state是只读的,修改state唯一方法就是触发action
- 使用纯函数 reducer来修改state,返回一个新的state给store
Redux工作流程:
- View在redux中会派发action方法
- action通过store的dispatch方法会派发给store
- store接收action,连同之前的state,一起传递给reducer
- reducer返回新的数据给store
- store去改变自己的state\
Redux 中异步的请求怎么处理
可以在 componentDidmount 中直接进⾏请求⽆须借助redux,但是一定规模的项目,会借助redux的异步中间件进⾏异步处理
主流的异步中间件有两种:redux-thunk、redux-saga
react-thunk中间件
优点:
- 体积⼩: redux-thunk的实现⽅式很简单,只有不到20⾏代码
- 使⽤简单: redux-thunk没有引⼊像redux-saga或者redux-observable额外的范式,上⼿简单
缺陷:
- 样板代码过多: 与redux本身⼀样,通常⼀个请求需要⼤量的代码,⽽且很多都是重复性质的
- 耦合严重: 异步操作与redux的action偶合在⼀起,不⽅便管理
- 功能孱弱: 有⼀些实际开发中常⽤的功能需要⾃⼰进⾏封装
使用步骤:
- 配置中间件,在store的创建中配置
- 添加一个返回函数的actionCreator,将异步请求逻辑放在里面
- 生成action,并发送action
使用 redux-saga 中间件
优点:
- 异步解耦: 异步操作被被转移到单独 saga.js 中,不再是掺杂在 action.js 或 component.js 中
- action摆脱thunk function: dispatch 的参数依然是⼀个纯粹的 action (FSA),⽽不是充满 “⿊魔法” thunk function
- 异常处理: 受益于 generator function 的 saga 实现,代码异常/请求失败 都可以直接通过 try/catch 语法直接捕获处理
- 功能强⼤: redux-saga提供了⼤量的Saga 辅助函数和Effect 创建器供开发者使⽤,开发者⽆须封装或者简单封装即可使⽤
- 灵活: redux-saga可以将多个Saga可以串⾏/并⾏组合起来,形成⼀个⾮常实⽤的异步flow
- 易测试,提供了各种case的测试⽅案,包括mock task,分⽀覆盖等等
缺陷:
- 额外的学习成本: redux-saga不仅在使⽤难以理解的 generator function,⽽且有数⼗个API,学习成本远超redux-thunk,最重要的是额外学习成本是只服务于这个库的,与redux-observable不同,redux-observable虽然也有额外学习成本但是背后是RxJS和⼀整套思想
- 体积庞⼤: 体积略⼤,代码近2000⾏,min版25KB左右
- 功能过剩: 实际上并发控制等功能很难⽤到,但是我们依然需要引⼊这些代码
- ts⽀持不友好: yield⽆法返回TS类型
redux-saga可以捕获action,然后执行一个函数,那么可以把异步代码放在这个函数中,步骤:
- 配置中间件
- 将异步请求放在sagas.js中
- 发送action
Redux 怎么实现属性传递,介绍下原理
react-redux 数据传输∶ view-->action-->reducer-->store-->view。
Redux 中间件是怎么拿到 store 和 action?然后怎么处理?
redux中间件本质就是一个函数柯里化。redux applyMiddleware Api 源码中每个middleware接收2个参数,Store的getState函数和dispatch函数,分别获得store和action,最终返回一个函数。该函数会被传入next的下一个midlleware和dispatch方法,并返回一个接收action的新函数,这个函数可以直接调用next(action),或者在其他需要的时刻调用,甚至根本不去调用它。调用链中最后middleware会接受真实的store的dispatch方法作为next参数,并借此结束调用链。所以,midlleware的函数签名是({getState,dispatch})=>next=>action
Redux 中的 connect 有什么作用
connect 负责连接 React和Redux
(1) 获取state:connect通过context 获取Provider中的store,通过store.getState()获取整个store tree上所有state
(2) 包装原组件:将state和action通过props的方式传入到原组件内部 wrapWithConnect返回一个ReactComponent对象Connect,Connect重新render外部传入的原组件WrappedComponent,并把connect中传入的mapStateToProps,mapDispatchToProps与组件上原有的props合并后,通过属性的方式传给WrappedComponent
(3) 监听store tree变化:connect缓存了store tree的中state,通过当前state状态 和变更前state状态进行比较,从而确实是否调用this.setState()方法触发Connect及其子组件的重新渲染
Redux 和 Vuex
本质:redux与vuex都是对mvvm思想的服务,将数据从视图中抽离的一种方案
共同思想:
- 单—的数据源
- 变化可以预测
区别:
- Vuex改进了Redux中的Action和Reducer函数,以mutations变化函数取代Reducer,无需switch,只需在对应的mutation函数里改变state值即可
- Vuex由于Vue自动重新渲染的特性,无需订阅重新渲染函数,只要生成新的State即可
- Vuex数据流的顺序是∶View调用store.commit提交对应的请求到Store中对应的mutation函数->store改变(vue检测到数据变化自动渲染)
Hooks
Hook 是 React 16.8 的新增特性。它可以在不编写 class 的情况下使用 state 以及其他的 React 特性。
React Hooks
解决了哪些问题
- 在组件之间复用状态逻辑很难
- 复杂组件变得难以理解
- 难以理解的 class
API 索引
基础 Hook:
(1) useState:useState可以弥补函数组件没有state的缺陷。useState可以接受一个初识值,也可以是一个函数action,action返回值作为新的state。返回一个数组,第一个值为state读取值,第二个值为改变state的dispatchAction函数。
(2) useEffect:可以弥补函数组件没有生命周期的缺点,用作数据交互、事件监听,还有一些基于dom的操作。可以在useEffect第一个参数回调函数中,做一些请求数据,事件监听等操作,第二个参数作为dep依赖项,当依赖项发生变化,重新执行第一个函数。注意:在useEffect第一个参数回调函数,返一个函数用于清除事件监听等操作。
(3) useContext:可以使用 useContext ,来获取父级组件传递过来的 context 值,这个当前值就是最近的父级组件 Provider 设置的 value 值,useContext 参数一般是由 createContext 方式引入 ,也可以父级上下文 context 传递 ( 参数为 context )。useContext 可以代替 context.Consumer 来获取 Provider 中保存的 value 值
额外的 Hook:
(1) useReducer:useState底层就是一个简单版的useReducer。useReducer 接受的第一个参数是一个函数,可以认为它就是一个 reducer , reducer 的参数就是常规 reducer 里面的 state 和 action ,返回改变后的 state , useReducer 第二个参数为 state 的初始值 返回一个数组,数组的第一项就是更新之后 state 的值 ,第二个参数是派发更新的 dispatch 函数。
(2) useCallback:useMemo 和 useCallback 接收的参数都是一样,都是在其依赖项发生变化后才执行,都是返回缓存的值,区别在于 useMemo 返回的是函数运行的结果, useCallback 返回的是函数。 返回的callback可以作为props回调函数传递给子组件。
(3) useMemo:useMemo接受两个参数,第一个参数是一个函数,返回值用于产生保存值。 第二个参数是一个数组,作为dep依赖项,数组里面的依赖项发生变化,重新执行第一个函数,产生新的值。
应用场景:
- 缓存一些值,避免重新执行上下文
- 减少不必要的
dom循环 - 减少子组件渲染
(4) useRef:
useRef作用:
- 可以用来获取
dom元素,或者class组件实例 。 react-hooks原理文章中讲过,创建useRef时候,会创建一个原始对象,只要函数组件不被销毁,原始对象就会一直存在,那么我们可以利用这个特性,来通过useRef保存一些数据。
useImperativeHandle
useLayoutEffect
useDebugValue
在平时开发中需要注意的问题
- 不要在循环、条件或嵌套函数中调用Hook,必须始终在React函数的顶层使用Hook
- 使用useState时候,使用push,pop,splice等直接更改数组对象的坑
- useState设置状态的时候,只有第一次生效,后期需要更新状态,必须通过useEffect
- 善用useCallback
- 不要滥用useContext:可以使用基于 useContext 封装的状态管理工具。
虚拟DOM
虚拟DOM是对DOM的抽象,这个对象是更加轻量级的对DOM的描述。它设计的最初目的,就是更好的跨平台
React diff 算法的原理是什么
diff 算法探讨的就是虚拟 DOM 树发生变化后,生成 DOM 树更新补丁的方式。它通过对比新旧两株虚拟 DOM 树的变更差异,将更新补丁作用于真实 DOM,以最小成本完成视图更新。
其他
在 React 中遍历的方法有哪些
- 遍历数组:map && forEach
- 遍历对象:map && for in
在React中页面重新加载时怎样保留数据
数据持久化方式:
- Redux: 将页面的数据存储在redux中,在重新加载页面时,获取Redux中的数据;
- data.js:使用webpack构建的项目,可以建一个文件,data.js,将数据保存data.js中,跳转页面后获取;
- sessionStorge: 在进入选择地址页面之前,componentWillUnMount的时候,将数据存储到sessionStorage中,每次进入页面判断sessionStorage中有没有存储的那个值,有,则读取渲染数据;没有,则说明数据是初始化的状态。返回或进入除了选择地址以外的页面,清掉存储的sessionStorage,保证下次进入是初始化的数据
- history API: History API 的
pushState函数可以给历史记录关联一个任意的可序列化state,所以可以在路由push的时候将当前页面的一些信息存到state中,下次返回到这个页面的时候就能从state里面取出离开前的数据重新渲染。react-router 直接可以支持。这个方法适合一些需要临时存储的场景。
React 有哪些性能优化的手段?
- 使用纯组件;
- 使用 React.memo 进行组件记忆(React.memo 是一个高阶组件),对于相同的输入,不重复执行;
- 如果是类组件,使用 shouldComponentUpdate(这是在重新渲染组件之前触发的其中一个生命周期事件)生命周期事件,可以利用此事件来决定何时需要重新渲染组件;
- 路由懒加载;
- 使用 React Fragments 避免额外标记;
- 不要使用内联函数定义(如果我们使用内联函数,则每次调用“render”函数时都会创建一个新的函数实例);
- 避免在Willxxx系列的生命周期中进行异步请求,操作dom等;
- 如果是类组件,事件函数在Constructor中绑定bind改变this指向;
- 避免使用内联样式属性;
- 优化 React 中的条件渲染;
- 不要在 render 方法中导出数据;
- 列表渲染的时候加key;
- 在函数组件中使用useCallback和useMemo来进行组件优化,依赖没有变化的话,不重复执行;
- 类组件中使用immutable对象;