ToB企服应用市场:ToB评测及商务社交产业平台

标题: 前端之React篇 [打印本页]

作者: 泉缘泉    时间: 2024-10-1 16:21
标题: 前端之React篇


一、组件基础


1. React 事件机制


  1. <div onClick={this.handleClick.bind(this)}>点我</div>
复制代码

React并不是将click事件绑定到了div的真实DOM上,而是在document处监听了全部的事件,当事件发生而且冒泡到document处的时候,React将事件内容封装并交由真正的处理函数运行。如许的方式不但仅淘汰了内存的消耗,还能在组件挂在烧毁时统一订阅和移除事件。

除此之外,冒泡到document上的事件也不是原生的浏览器事件,而是由react自己实现的合成事件(SyntheticEvent)。因此如果不想要是事件冒泡的话应该调用event.preventDefault()方法,而不是调用event.stopProppagation()方法。




JSX 上写的事件并没有绑定在对应的真实 DOM 上,而是通过事件代理的方式,将全部的事件都统一绑定在了 document 上。如许的方式不但淘汰了内存消耗,还能在组件挂载烧毁时统一订阅和移除事件。

另外冒泡到 document 上的事件也不是原生浏览器事件,而是 React 自己实现的合成事件(SyntheticEvent)。因此我们如果不想要事件冒泡的话,调用 event.stopPropagation 是无效的,而应该调用 event.preventDefault。

实现合成事件的目标如下:



2. React的事件和普通的HTML事件有什么不同?


区别:



合成事件是 react 模仿原生 DOM 事件全部本领的一个事件对象,其优点如下:



事件的执行顺序为原生事件先执行,合成事件后执行,合成事件会冒泡绑定到 document 上,所以尽量避免原生事件与合成事件混用,如果原生事件制止冒泡,大概会导致合成事件不执行,因为需要冒泡到document 上合成事件才会执行。

3. React 组件中怎么做事件代理?它的原理是什么?


React基于Virtual DOM实现了一个SyntheticEvent层(合成事件层),界说的事件处理器会吸收到一个合成事件对象的实例,它符合W3C标准,且与原生的浏览器事件拥有同样的接口,支持冒泡机制,全部的事件都自动绑定在最外层上。

在React底层,主要对合成事件做了两件事:



4. React 高阶组件、Render props、hooks 有什么区别,为什么要不断迭代


这三者是目前react办理代码复用的主要方式:



(1)HOC

官方表明∶

高阶组件(HOC)是 React 中用于复用组件逻辑的一种高级技巧。HOC 自身不是 React API 的一部分,它是一种基于 React 的组合特性而形成的设计模式。

简言之,HOC是一种组件的设计模式,HOC担当一个组件和额外的参数(如果需要),返回一个新的组件。HOC 是纯函数,没有副作用。

  1. // hoc的定义
  2. function withSubscription(WrappedComponent, selectData) {
  3.   return class extends React.Component {
  4.     constructor(props) {
  5.       super(props);
  6.       this.state = {
  7.         data: selectData(DataSource, props)
  8.       };
  9.     }
  10.     // 一些通用的逻辑处理
  11.     render() {
  12.       // ... 并使用新数据渲染被包装的组件!
  13.       return <WrappedComponent data={this.state.data} {...this.props} />;
  14.     }
  15.   };
  16. // 使用
  17. const BlogPostWithSubscription = withSubscription(BlogPost,
  18.   (DataSource, props) => DataSource.getBlogPost(props.id));
复制代码

HOC的优缺点∶



(2)Render props

官方表明∶

"render prop"是指一种在 React 组件之间使用一个值为函数的 prop 共享代码的简单技能

具有render prop 的组件担当一个返回React元素的函数,将render的渲染逻辑注入到组件内部。在这里,"render"的定名可以是任何其他有效的标识符。

  1. // DataProvider组件内部的渲染逻辑如下
  2. class DataProvider extends React.Components {
  3.      state = {
  4.     name: 'Tom'
  5.   }
  6.     render() {
  7.     return (
  8.         <div>
  9.           <p>共享数据组件自己内部的渲染逻辑</p>
  10.           { this.props.render(this.state) }
  11.       </div>
  12.     );
  13.   }
  14. }
  15. // 调用方式
  16. <DataProvider render={data => (
  17.   <h1>Hello {data.name}</h1>
  18. )}/>
复制代码

由此可以看到,render props的优缺点也很明显∶



(3)Hooks

官方表明∶

Hook是 React 16.8 的新增特性。它可以让你在不编写 class 的情况下使用 state 以及其他的 React 特性。通过自界说hook,可以复用代码逻辑。

  1. // 自定义一个获取订阅数据的hook
  2. function useSubscription() {
  3.   const data = DataSource.getComments();
  4.   return [data];
  5. }
  6. //
  7. function CommentList(props) {
  8.   const {data} = props;
  9.   const [subData] = useSubscription();
  10.     ...
  11. }
  12. // 使用
  13. <CommentList data='hello' />
复制代码

以上可以看出,hook办理了hoc的prop覆盖的题目,同时使用的方式办理了render props的嵌套地狱的题目。hook的优点如下∶



需要注意的是:hook只能在组件顶层使用,不可在分支语句中使用。

总结∶

Hoc、render props和hook都是为了办理代码复用的题目,但是hoc和render props都有特定的使用场景和明显的缺点。hook是react16.8更新的新的API,让组件逻辑复用更简便明了,同时也办理了hoc和render props的一些缺点。

5. 对React-Fiber的理解,它办理了什么题目?


React V15 在渲染时,会递归比对 VirtualDOM 树,找出需要变更的节点,然后同步更新它们, 趁热打铁。这个过程期间, React 会占据浏览器资源,这会导致用户触发的事件得不到响应,而且会导致掉帧,导致用户感觉到卡顿

为了给用户制造一种应用很快的“假象”,不能让一个使命长期霸占着资源。 可以将浏览器的渲染、结构、绘制、资源加载(比方 HTML 解析)、事件响应、脚本执行视作操作体系的“进程”,需要通过某些调理计谋合理地分配 CPU 资源,从而进步浏览器的用户响应速率, 同时分身使命执行效率。

所以 React 通过Fiber 架构,让这个执行过程变成可被中断。“适时”地让出 CPU 执行权,除了可以让浏览器及时地响应用户的交互,还有其他好处:






焦点思想:Fiber 也称协程或者纤程。它和线程并不一样,协程本身是没有并发或者并行本领的(需要配合线程),它只是一种控制流程的让出机制。让出 CPU 的执行权,让 CPU 能在这段时间执行其他的操作。渲染的过程可以被中断,可以将控制权交回浏览器,让位给高优先级的使命,浏览器空闲后再规复渲染。

6. React.Component 和 React.PureComponent 的区别


PureComponent表示一个纯组件,可以用来优化React程序,淘汰render函数执行的次数,从而进步组件的性能。

在React中,当prop或者state发生变化时,可以通过在shouldComponentUpdate生命周期函数中执行return false来制止页面的更新,从而淘汰不须要的render执行。React.PureComponent会自动执行 shouldComponentUpdate。

不过,pureComponent中的 shouldComponentUpdate() 进行的是浅比较,也就是说如果是引用数据类型的数据,只会比较不是同一个地址,而不会比较这个地址内里的数据是否一致。浅比较会忽略属性和或状态突变情况,实在也就是数据引用指针没有变化,而数据发生改变的时候render是不会执行的。如果需要重新渲染那么就需要重新开辟空间引用数据。PureComponent一样平常会用在一些纯展示组件上。

使用pureComponent的好处:当组件更新时,如果组件的props或者state都没有改变,render函数就不会触发。省去虚拟DOM的生成和对比过程,到达提升性能的目标。这是因为react自动做了一层浅比较。

7. Component, Element, Instance 之间有什么区别和联系?




函数式组件(Functional component)根本没有实例instance。类组件(Class component)有实例instance,但是永久也不需要直接创建一个组件的实例,因为React帮我们做了这些。

8. React.createClass和extends Component的区别有哪些?


React.createClass和extends Component的bai区别主要在于:

(1)语法区别



(2)propType 和 getDefaultProps



(3)状态的区别



(4)this区别



(5)Mixins



9. React 高阶组件是什么,和普通组件有什么区别,实用什么场景


官方表明∶

高阶组件(HOC)是 React 中用于复用组件逻辑的一种高级技巧。HOC 自身不是 React API 的一部分,它是一种基于 React 的组合特性而形成的设计模式。

高阶组件(HOC)就是一个函数,且该函数担当一个组件作为参数,并返回一个新的组件,它只是一种组件的设计模式,这种设计模式是由react自身的组合性质一定产生的。我们将它们称为纯组件,因为它们可以担当任何动态提供的子组件,但它们不会修改或复制其输入组件中的任何举动。

  1. // hoc的定义
  2. function withSubscription(WrappedComponent, selectData) {
  3.   return class extends React.Component {
  4.     constructor(props) {
  5.       super(props);
  6.       this.state = {
  7.         data: selectData(DataSource, props)
  8.       };
  9.     }
  10.     // 一些通用的逻辑处理
  11.     render() {
  12.       // ... 并使用新数据渲染被包装的组件!
  13.       return <WrappedComponent data={this.state.data} {...this.props} />;
  14.     }
  15.   };
  16. // 使用
  17. const BlogPostWithSubscription = withSubscription(BlogPost,
  18.   (DataSource, props) => DataSource.getBlogPost(props.id));
复制代码

1)HOC的优缺点



2)实用场景



3)具体应用例子



  1. // HOC.js
  2. function withAdminAuth(WrappedComponent) {
  3.     return class extends React.Component {
  4.         state = {
  5.             isAdmin: false,
  6.         }
  7.         async UNSAFE_componentWillMount() {
  8.             const currentRole = await getCurrentUserRole();
  9.             this.setState({
  10.                 isAdmin: currentRole === 'Admin',
  11.             });
  12.         }
  13.         render() {
  14.             if (this.state.isAdmin) {
  15.                 return <WrappedComponent {...this.props} />;
  16.             } else {
  17.                 return (<div>您没有权限查看该页面,请联系管理员!</div>);
  18.             }
  19.         }
  20.     };
  21. }
  22. // pages/page-a.js
  23. class PageA extends React.Component {
  24.     constructor(props) {
  25.         super(props);
  26.         // something here...
  27.     }
  28.     UNSAFE_componentWillMount() {
  29.         // fetching data
  30.     }
  31.     render() {
  32.         // render page with data
  33.     }
  34. }
  35. export default withAdminAuth(PageA);
  36. // pages/page-b.js
  37. class PageB extends React.Component {
  38.     constructor(props) {
  39.         super(props);
  40.     // something here...
  41.         }
  42.     UNSAFE_componentWillMount() {
  43.     // fetching data
  44.     }
  45.     render() {
  46.     // render page with data
  47.     }
  48. }
  49. export default withAdminAuth(PageB);
复制代码



  1. class Home extends React.Component {
  2.         render() {
  3.             return (<h1>Hello World.</h1>);
  4.         }
  5.     }
  6.     function withTiming(WrappedComponent) {
  7.         return class extends WrappedComponent {
  8.             constructor(props) {
  9.                 super(props);
  10.                 this.start = 0;
  11.                 this.end = 0;
  12.             }
  13.             UNSAFE_componentWillMount() {
  14.                 super.componentWillMount && super.componentWillMount();
  15.                 this.start = Date.now();
  16.             }
  17.             componentDidMount() {
  18.                 super.componentDidMount && super.componentDidMount();
  19.                 this.end = Date.now();
  20.                 console.log(`${WrappedComponent.name} 组件渲染时间为 ${this.end - this.start} ms`);
  21.             }
  22.             render() {
  23.                 return super.render();
  24.             }
  25.         };
  26.     }
  27.     export default withTiming(Home);
复制代码

注意:withTiming 是利用 反向继承 实现的一个高阶组件,功能是盘算被包裹组件(这里是 Home 组件)的渲染时间。



  1. const withFetching = fetching => WrappedComponent => {
  2.     return class extends React.Component {
  3.         state = {
  4.             data: [],
  5.         }
  6.         async UNSAFE_componentWillMount() {
  7.             const data = await fetching();
  8.             this.setState({
  9.                 data,
  10.             });
  11.         }
  12.         render() {
  13.             return <WrappedComponent data={this.state.data} {...this.props} />;
  14.         }
  15.     }
  16. }
  17. // pages/page-a.js
  18. export default withFetching(fetching('science-fiction'))(MovieList);
  19. // pages/page-b.js
  20. export default withFetching(fetching('action'))(MovieList);
  21. // pages/page-other.js
  22. export default withFetching(fetching('some-other-type'))(MovieList);
复制代码

10. 对componentWillReceiveProps 的理解


该方法当props发生变化时执行,初始化render时不执行,在这个回调函数内里,你可以根据属性的变化,通过调用this.setState()来更新你的组件状态,旧的属性还是可以通过this.props来获取,这里调用更新状态是安全的,并不会触发额外的render调用。

使用好处:在这个生命周期中,可以在子组件的render函数执行前获取新的props,从而更新子组件自己的state。 可以将数据哀求放在这里进行执行,需要传的参数则从componentWillReceiveProps(nextProps)中获取。而不必将全部的哀求都放在父组件中。于是该哀求只会在该组件渲染时才会发出,从而减轻哀求负担。componentWillReceiveProps在初始化render的时候不会执行,它会在Component担当到新的状态(Props)时被触发,一样平常用于父组件状态更新时子组件的重新渲染。

11. 哪些方法会触发 React 重新渲染?重新渲染 render 会做些什么?


(1)哪些方法会触发 react 重新渲染?



setState 是 React 中最常用的命令,通常情况下,执行 setState 会触发 render。但是这里有个点值得关注,执行 setState 的时候不一定会重新渲染。当 setState 传入 null 时,并不会触发 render。

  1. class App extends React.Component {
  2.   state = {
  3.     a: 1
  4.   };
  5.   render() {
  6.     console.log("render");
  7.     return (
  8.       <React.Fragement>
  9.         <p>{this.state.a}</p>
  10.         <button
  11.           onClick={() => {
  12.             this.setState({ a: 1 }); // 这里并没有改变 a 的值
  13.           }}
  14.         >
  15.           Click me
  16.         </button>
  17.         <button onClick={() => this.setState(null)}>setState null</button>
  18.         <Child />
  19.       </React.Fragement>
  20.     );
  21.   }
  22. }
复制代码



只要父组件重新渲染了,即使传入子组件的 props 未发生变化,那么子组件也会重新渲染,进而触发 render

(2)重新渲染 render 会做些什么?



React 的处理 render 的根本思维模式是每次一有变更就会去重新渲染整个应用。在 Virtual DOM 没有出现之前,最简单的方法就是直接调用 innerHTML。Virtual DOM锋利的地方并不是说它比直接操作 DOM 快,而是说不管数据怎么变,都会尽量以最小的代价去更新 DOM。React 将 render 函数返回的虚拟 DOM 树与老的进行比较,从而确定 DOM 要不要更新、怎么更新。当 DOM 树很大时,遍历两棵树进行各种比对还是相称耗性能的,特殊是在顶层 setState 一个微小的修改,默认会去遍历整棵树。尽管 React 使用高度优化的 Diff 算法,但是这个过程仍然会损耗性能.

12. React如何判断什么时候重新渲染组件?


组件状态的改变可以因为props的改变,或者直接通过setState方法改变。组件获得新的状态,然后React决定是否应该重新渲染组件。只要组件的state发生变化,React就会对组件进行重新渲染。这是因为React中的shouldComponentUpdate方法默认返回true,这就是导致每次更新都重新渲染的原因。

当React将要渲染组件时会执行shouldComponentUpdate方法来看它是否返回true(组件应该更新,也就是重新渲染)。所以需要重写shouldComponentUpdate方法让它根据情况返回true或者false来告诉React什么时候重新渲染什么时候跳过重新渲染。

13. React声明组件有哪几种方法,有什么不同?


React 声明组件的三种方式:



(1)无状态函数式组件

它是为了创建纯展示组件,这种组件只负责根据传入的props来展示,不涉及到state状态的操作

组件不会被实例化,整体渲染性能得到提升,不能访问this对象,不能访问生命周期的方法

(2)ES5 原生方式 React.createClass // RFC

React.createClass会自绑定函数方法,导致不须要的性能开销,增长代码过期的大概性。

(3)E6继承形式 React.Component // RCC

目前极为保举的创建有状态组件的方式,最终会取代React.createClass形式;相对于 React.createClass可以更好实现代码复用。

无状态组件相对于于后者的区别:

与无状态组件相比,React.createClass和React.Component都是创建有状态的组件,这些组件是要被实例化的,而且可以访问组件的生命周期方法。

React.createClass与React.Component区别:

① 函数this自绑定



② 组件属性类型propTypes及其默认props属性defaultProps配置不同



③ 组件初始状态state的配置不同



14. 对有状态组件和无状态组件的理解及使用场景


(1)有状态组件

特点:



使用场景:



总结:

类组件可以维护自身的状态变量,即组件的 state ,类组件还有不同的生命周期方法,可以让开发者可以大概在组件的不同阶段(挂载、更新、卸载),对组件做更多的控制。类组件则既可以充当无状态组件,也可以充当有状态组件。当一个类组件不需要管理自身状态时,也可称为无状态组件。

(2)无状态组件

特点:



使用场景:



优点:



缺点:



总结:

组件内部状态且与外部无关的组件,可以考虑用状态组件,如许状态树就不会过于复杂,易于理解和管理。当一个组件不需要管理自身状态时,也就是无状态组件,应该优先设计为函数组件。好比自界说的 <Button/>、 <Input /> 等组件。

15. 对React中Fragment的理解,它的使用场景是什么?


在React中,组件返回的元素只能有一个根元素。为了不添加多余的DOM节点,我们可以使用Fragment标签来包裹全部的元素,Fragment标签不会渲染出任何元素。React官方对Fragment的表明:

React 中的一个常见模式是一个组件返回多个元素。Fragments 允许你将子列表分组,而无需向 DOM 添加额外节点。

  1. import React, { Component, Fragment } from 'react'
  2. // 一般形式
  3. render() {
  4.   return (
  5.     <React.Fragment>
  6.       <ChildA />
  7.       <ChildB />
  8.       <ChildC />
  9.     </React.Fragment>
  10.   );
  11. }
  12. // 也可以写成以下形式
  13. render() {
  14.   return (
  15.     <>
  16.       <ChildA />
  17.       <ChildB />
  18.       <ChildC />
  19.     </>
  20.   );
  21. }
复制代码

16. React如何获取组件对应的DOM元素?


可以用ref来获取某个子节点的实例,然后通过当前class组件实例的一些特定属性来直接获取子节点实例。ref有三种实现方法:



17. React中可以在render访问refs吗?为什么?


  1. <>
  2.   <span id="name" ref={this.spanRef}>{this.state.title}</span>
  3.   <span>{
  4.      this.spanRef.current ? '有值' : '无值'
  5.   }</span>
  6. </>
复制代码

不可以,render 阶段 DOM 还没有生成,无法获取 DOM。DOM 的获取需要在 pre-commit 阶段和 commit 阶段:




18. 对React的插槽(Portals)的理解,如何使用,有哪些使用场景


React 官方对 Portals 的界说:

Portal 提供了一种将子节点渲染到存在于父组件以外的 DOM 节点的良好的方案

Portals 是React 16提供的官方办理方案,使得组件可以脱离父组件层级挂载在DOM树的任何位置。通俗来讲,就是我们 render 一个组件,但这个组件的 DOM 结构并不在本组件内。

Portals语法如下:

  1. ReactDOM.createPortal(child, container);
复制代码



一样平常情况下,组件的render函数返回的元素会被挂载在它的父级组件上:

  1. import DemoComponent from './DemoComponent';
  2. render() {
  3.   // DemoComponent元素会被挂载在id为parent的div的元素上
  4.   return (
  5.     <div id="parent">
  6.         <DemoComponent />
  7.     </div>
  8.   );
  9. }
复制代码

然而,有些元素需要被挂载在更高层级的位置。最典型的应用场景:当父组件具有overflow: hidden或者z-index的样式设置时,组件有大概被其他元素遮挡,这时就可以考虑要不要使用Portal使组件的挂载脱离父组件。比方:对话框,模态窗。

  1. import DemoComponent from './DemoComponent';
  2. render() {
  3.   // react会将DemoComponent组件直接挂载在真实的 dom 节点 domNode 上,生命周期还和16版本之前相同。
  4.   return ReactDOM.createPortal(
  5.     <DemoComponent />,
  6.     domNode,
  7.   );
  8. }
复制代码

19. 在React中如何避免不须要的render?


React 基于虚拟 DOM 和高效 Diff 算法的完美配合,实现了对 DOM 最小粒度的更新。大多数情况下,React 对 DOM 的渲染效率足以业务日常。但在个别复杂业务场景下,性能题目依然会困扰我们。此时需要采取一些步伐来提升运行性能,其很重要的一个方向,就是避免不须要的渲染(Render)。这里提下优化的点:



在 React 类组件中,可以利用 shouldComponentUpdate或者 PureComponent 来淘汰因父组件更新而触发子组件的 render,从而到达目标。shouldComponentUpdate 来决定是否组件是否重新渲染,如果不盼望组件重新渲染,返回 false 即可。



在函数组件中,并没有 shouldComponentUpdate 这个生命周期,可以利用高阶组件,封装一个类似 PureComponet 的功能



React.memo 是 React 16.6 新的一个 API,用来缓存组件的渲染,避免不须要的更新,实在也是一个高阶组件,与 PureComponent 十分类似,但不同的是, React.memo只能用于函数组件。

20. 对 React-Intl 的理解,它的工作原理?


React-intl是雅虎的语言国际化开源项目FormatJS的一部分,通过其提供的组件和API可以与ReactJS绑定。

React-intl提供了两种使用方法,一种是引用React组件,另一种是直接调取API,官方更加保举在React项目中使用前者,只有在无法使用React组件的地方,才应该调用框架提供的API。它提供了一系列的React组件,包括数字格式化、字符串格式化、日期格式化等。

在React-intl中,可以配置不同的语言包,他的工作原理就是根据需要,在语言包之间进行切换。

21. 对 React context 的理解


在React中,数据转达一样平常使用props转达数据,维持单向数据流,如许可以让组件之间的关系变得简单且可预测,但是单项数据流在某些场景中并不实用。单纯一对的父子组件转达并无题目,但要是组件之间层层依赖深入,props就需要层层转达显然,如许做太繁琐了。

Context 提供了一种在组件之间共享此类值的方式,而不必显式地通过组件树的逐层转达 props。

可以把context当做是特定一个组件树内共享的store,用来做数据转达。简单说就是,当你不想在组件树中通过逐层转达props或者state的方式来转达数据时,可以使用Context来实现跨层级的组件数据转达。

JS的代码块在执行期间,会创建一个相应的作用域链,这个作用域链记录着运行时JS代码块执行期间所能访问的活动对象,包括变量和函数,JS程序通过作用域链访问到代码块内部或者外部的变量和函数。

假如以JS的作用域链作为类比,React组件提供的Context对象实在就好比一个提供给子组件访问的作用域,而 Context对象的属性可以看成作用域上的活动对象。由于组件 的 Context 由其父节点链上全部组件通 过 getChildContext()返回的Context对象组合而成,所以,组件通过Context是可以访问到其父组件链上全部节点组件提供的Context的属性。

22. 为什么React并不保举优先考虑使用Context?




23. React中什么是受控组件和非控组件?


(1)受控组件

在使用表单来网络用户输入时,比方等元素都要绑定一个change事件,当表单的状态发生变化,就会触发onChange事件,更新组件的state。这种组件在React中被称为受控组件,在受控组件中,组件渲染出的状态与它的value或checked属性相对应,react通过这种方式消除了组件的局部状态,使整个状态可控。react官方保举使用受控表单组件。

受控组件更新state的流程:



受控组件缺陷:

表单元素的值都是由React组件进行管理,当有多个输入框,或者多个这种组件时,如果想同时获取到全部的值就必须每个都要编写事件处理函数,这会让代码看着很痴肥,所以为了办理这种情况,出现了非受控组件。

(2)非受控组件

如果一个表单组件没有value props(单选和复选按钮对应的是checked props)时,就可以称为非受控组件。在非受控组件中,可以使用一个ref来从DOM获得表单值。而不是为每个状态更新编写一个事件处理程序。

React官方的表明:

要编写一个非受控组件,而不是为每个状态更新都编写数据处理函数,你可以使用 ref来从 DOM 节点中获取表单数据。

因为非受控组件将真实数据储存在 DOM 节点中,所以在使用非受控组件时,有时候反而更轻易同时集成 React 和非 React 代码。如果你不介意代码雅观性,而且盼望快速编写代码,使用非受控组件每每可以淘汰你的代码量。否则,你应该使用受控组件。

比方,下面的代码在非受控组件中吸收单个属性:

  1. class NameForm extends React.Component {
  2.   constructor(props) {
  3.     super(props);
  4.     this.handleSubmit = this.handleSubmit.bind(this);
  5.   }
  6.   handleSubmit(event) {
  7.     alert('A name was submitted: ' + this.input.value);
  8.     event.preventDefault();
  9.   }
  10.   render() {
  11.     return (
  12.       <form onSubmit={this.handleSubmit}>
  13.         <label>
  14.           Name:
  15.           <input type="text" ref={(input) => this.input = input} />
  16.         </label>
  17.         <input type="submit" value="Submit" />
  18.       </form>
  19.     );
  20.   }
  21. }
复制代码

总结:页面中全部输入类的DOM如果是现用现取的称为非受控组件,而通过setState将输入的值维护到了state中,需要时再从state中取出,这里的数据就受到了state的控制,称为受控组件。

24. React中refs的作用是什么?有哪些应用场景?


Refs 提供了一种方式,用于访问在 render 方法中创建的 React 元素或 DOM 节点。Refs 应该谨慎使用,如下场景使用 Refs 比较得当:



Refs 是使用 React.createRef() 方法创建的,他通过 ref 属性附加到 React 元素上。要在整个组件中使用 Refs,需要将 ref 在构造函数中分配给实在例属性:

  1. class MyComponent extends React.Component {
  2.   constructor(props) {
  3.     super(props)
  4.     this.myRef = React.createRef()
  5.   }
  6.   render() {
  7.     return <div ref={this.myRef} />
  8.   }
  9. }
复制代码

由于函数组件没有实例,因此不能在函数组件上直接使用 ref:

  1. function MyFunctionalComponent() {
  2.   return <input />;
  3. }
  4. class Parent extends React.Component {
  5.   constructor(props) {
  6.     super(props);
  7.     this.textInput = React.createRef();
  8.   }
  9.   render() {
  10.     // 这将不会工作!
  11.     return (
  12.       <MyFunctionalComponent ref={this.textInput} />
  13.     );
  14.   }
  15. }
复制代码

但可以通过闭合的资助在函数组件内部进行使用 Refs:

  1. function CustomTextInput(props) {
  2.   // 这里必须声明 textInput,这样 ref 回调才可以引用它
  3.   let textInput = null;
  4.   function handleClick() {
  5.     textInput.focus();
  6.   }
  7.   return (
  8.     <div>
  9.       <input
  10.         type="text"
  11.         ref={(input) => { textInput = input; }} />
  12.       <input
  13.         type="button"
  14.         value="Focus the text input"
  15.         onClick={handleClick}
  16.       />
  17.     </div>
  18.   );  
  19. }
复制代码

注意:





25. React中除了在构造函数中绑定this,还有别的方式吗?




  1. constructor(props){
  2.       super(props);
  3.        this.state={
  4.            msg:'hello world',
  5.        }
  6.        this.getMsg = this.getMsg.bind(this)
  7.    }
复制代码



  1. constructor(props){
  2.     super(props);
  3.     this.state={
  4.            msg:'hello world',
  5.     }
  6.     render(){
  7.       <button onClcik={()=>{alert(this.state.msg)}}>点我</button>
  8.     }
  9. }
复制代码



  1. <button onClick={this.getMsg.bind(this)}>点我</button>
复制代码

26. React组件的构造函数有什么作用?它是必须的吗?


构造函数主要用于两个目标:



所以,当在React class中需要设置state的初始值或者绑定事件时,需要加上构造函数,官方Demo:

  1. class LikeButton extends React.Component {
  2.   constructor() {
  3.     super();
  4.     this.state = {
  5.       liked: false
  6.     };
  7.     this.handleClick = this.handleClick.bind(this);
  8.   }
  9.   handleClick() {
  10.     this.setState({liked: !this.state.liked});
  11.   }
  12.   render() {
  13.     const text = this.state.liked ? 'liked' : 'haven\'t liked';
  14.     return (
  15.       <div onClick={this.handleClick}>
  16.         You {text} this. Click to toggle.
  17.       </div>
  18.     );
  19.   }
  20. }
  21. ReactDOM.render(
  22.   <LikeButton />,
  23.   document.getElementById('example')
  24. );
复制代码

构造函数用来新建父类的this对象;子类必须在constructor方法中调用super方法;否则新建实例时会报错;因为子类没有自己的this对象,而是继承父类的this对象,然后对其进行加工。如果不调用super方法;子类就得不到this对象。

注意:



27. React.forwardRef是什么?它有什么作用?


React.forwardRef 会创建一个React组件,这个组件可以大概将其担当的 ref 属性转发到其组件树下的另一个组件中。这种技能并不常见,但在以下两种场景中特殊有用:



28. 类组件与函数组件有什么异同?


相同点:

组件是 React 可复用的最小代码片段,它们会返回要在页面中渲染的 React 元素。也正因为组件是 React 的最小编码单位,所以无论是函数组件还是类组件,在使用方式和最终出现效果上都是完全一致的。

我们甚至可以将一个类组件改写成函数组件,或者把函数组件改写成一个类组件(固然并不保举这种重构举动)。从使用者的角度而言,很难从使用体验上区分两者,而且在现代浏览器中,闭包和类的性能只在极端场景下才会有明显的差异。所以,根本可认为两者作为组件是完全一致的。

不同点:



二、数据管理


1. React setState 调用的原理





具体的执行过程如下(源码级解析):



  1. ReactComponent.prototype.setState = function (partialState, callback) {
  2.   this.updater.enqueueSetState(this, partialState);
  3.   if (callback) {
  4.     this.updater.enqueueCallback(this, callback, 'setState');
  5.   }
  6. };
复制代码



  1. enqueueSetState: function (publicInstance, partialState) {
  2.   // 根据 this 拿到对应的组件实例
  3.   var internalInstance = getInternalInstanceReadyForUpdate(publicInstance, 'setState');
  4.   // 这个 queue 对应的就是一个组件实例的 state 数组
  5.   var queue = internalInstance._pendingStateQueue || (internalInstance._pendingStateQueue = []);
  6.   queue.push(partialState);
  7.   //  enqueueUpdate 用来处理当前的组件实例
  8.   enqueueUpdate(internalInstance);
  9. }
复制代码



  1. function enqueueUpdate(component) {
  2.   ensureInjected();
  3.   // 注意这一句是问题的关键,isBatchingUpdates标识着当前是否处于批量创建/更新组件的阶段
  4.   if (!batchingStrategy.isBatchingUpdates) {
  5.     // 若当前没有处于批量创建/更新组件的阶段,则立即更新组件
  6.     batchingStrategy.batchedUpdates(enqueueUpdate, component);
  7.     return;
  8.   }
  9.   // 否则,先把组件塞入 dirtyComponents 队列里,让它“再等等”
  10.   dirtyComponents.push(component);
  11.   if (component._updateBatchNumber == null) {
  12.     component._updateBatchNumber = updateBatchNumber + 1;
  13.   }
  14. }
复制代码

注意:batchingStrategy 对象可以理解为“锁管理器”。这里的“锁”,是指 React 全局唯一的 isBatchingUpdates 变量,isBatchingUpdates 的初始值是 false,意味着“当前并未进行任何批量更新操作”。每当 React 调用 batchedUpdate 去执行更新动作时,会先把这个锁给“锁上”(置为 true),表明“如今正处于批量更新过程中”。当锁被“锁上”的时候,任何需要更新的组件都只能暂时进入 dirtyComponents 里排队期待下一次的批量更新,而不能随意“插队”。此处表现的“使命锁”的思想,是 React 面临大量状态仍然可以大概实现有序分批处理的基石。

2. React setState 调用之后发生了什么?是同步还是异步?


(1)React中setState后发生了什么

在代码中调用setState函数之后,React 会将传入的参数对象与组件当前的状态合并,然后触发调和过程(Reconciliation)。经过调和过程,React 会以相对高效的方式根据新的状态构建 React 元素树而且着手重新渲染整个UI界面。

在 React 得到元素树之后,React 会自动盘算出新的树与老树的节点差异,然后根据差异对界面进行最小化重渲染。在差异盘算算法中,React 可以大概相对精确地知道哪些位置发生了改变以及应该如何改变,这就保证了按需更新,而不是全部重新渲染。

如果在短时间内频繁setState。React会将state的改变压入栈中,在合适的机会,批量更新state和视图,到达进步性能的效果。

(2)setState 是同步还是异步的

假如全部setState是同步的,意味着每执行一次setState时(有大概一个同步代码中,多次setState),都重新vnode diff + dom修改,这对性能来说是极为不好的。如果是异步,则可以把一个同步代码中的多个setState合并成一次组件更新。所以默认是异步的,但是在一些情况下是同步的。

setState 并不是单纯同步/异步的,它的表现会因调用场景的不同而不同。在源码中,通过 isBatchingUpdates 来判断setState 是先存进 state 队列还是直接更新,如果值为 true 则执行异步操作,为 false 则直接更新。



一样平常认为,做异步设计是为了性能优化、淘汰渲染次数:



3. React中的setState批量更新的过程是什么?


调用 setState 时,组件的 state 并不会立刻改变, setState 只是把要修改的 state 放入一个队列, React 会优化真正的执行机会,并出于性能原因,会将 React 事件处理程序中的多次React 事件处理程序中的多次 setState 的状态修改合并成一次状态修改。 最终更新只产生一次组件及其子组件的重新渲染,这对于大型应用程序中的性能提升至关重要。

  1. this.setState({
  2.   count: this.state.count + 1    ===>    入队,[count+1的任务]
  3. });
  4. this.setState({
  5.   count: this.state.count + 1    ===>    入队,[count+1的任务,count+1的任务]
  6. });
  7.                                           ↓
  8.                                          合并 state,[count+1的任务]
  9.                                           ↓
  10.                                          执行 count+1的任务
复制代码

需要注意的是,只要同步代码还在执行,“攒起来”这个动作就不会制止。(注:这里之所以多次 +1 最终只有一次生效,是因为在同一个方法中多次 setState 的合并动作不是单纯地将更新累加。好比这里对于相同属性的设置,React 只会为其保留末了一次的更新)。

4.  React中有使用过getDefaultProps吗?它有什么作用?


通过实现组件的getDefaultProps,对属性设置默认值(ES5的写法):

  1. var ShowTitle = React.createClass({
  2.   getDefaultProps:function(){
  3.     return{
  4.       title : "React"
  5.     }
  6.   },
  7.   render : function(){
  8.     return <h1>{this.props.title}</h1>
  9.   }
  10. });
复制代码

5. React中setState的第二个参数作用是什么?


setState 的第二个参数是一个可选的回调函数。这个回调函数将在组件重新渲染后执行。等价于在 componentDidUpdate 生命周期内执行。通常建议使用 componentDidUpdate 来取代此方式。在这个回调函数中你可以拿到更新后 state 的值:

  1. this.setState({
  2.     key1: newState1,
  3.     key2: newState2,
  4.     ...
  5. }, callback) // 第二个参数是 state 更新完成后的回调函数
复制代码

6. React中的setState和replaceState的区别是什么?


(1)setState()

setState()用于设置状态对象,其语法如下:

  1. setState(object nextState[, function callback])
复制代码



合并nextState和当前state,并重新渲染组件。setState是React事件处理函数中和哀求回调函数中触发UI更新的主要方法。

(2)replaceState()

replaceState()方法与setState()类似,但是方法只会保留nextState中状态,原state不在nextState中的状态都会被删除。其语法如下:

  1. replaceState(object nextState[, function callback])
复制代码



总结:setState 是修改此中的部分状态,相称于 Object.assign,只是覆盖,不会淘汰原来的状态。而replaceState 是完全替换原来的状态,相称于赋值,将原来的 state 替换为另一个对象,如果新状态属性淘汰,那么 state 中就没有这个状态了。

7. 在React中组件的this.state和setState有什么区别?


this.state通常是用来初始化state的,this.setState是用来修改state值的。如果初始化了state之后再使用this.state,之前的state会被覆盖掉,如果使用this.setState,只会替换掉相应的state值。所以,如果想要修改state的值,就需要使用setState,而不能直接修改state,直接修改state之后页面是不会更新的。

8. state 是怎么注入到组件的,从 reducer 到组件经历了什么样的过程


通过connect和mapStateToProps将state注入到组件中:

  1. import { connect } from 'react-redux'
  2. import { setVisibilityFilter } from '@/reducers/Todo/actions'
  3. import Link from '@/containers/Todo/components/Link'
  4. const mapStateToProps = (state, ownProps) => ({
  5.     active: ownProps.filter === state.visibilityFilter
  6. })
  7. const mapDispatchToProps = (dispatch, ownProps) => ({
  8.     setFilter: () => {
  9.         dispatch(setVisibilityFilter(ownProps.filter))
  10.     }
  11. })
  12. export default connect(
  13.     mapStateToProps,
  14.     mapDispatchToProps
  15. )(Link)
复制代码

上面代码中,active就是注入到Link组件中的状态。 mapStateToProps(state,ownProps)中带有两个参数,含义是∶



reducer 到组件经历的过程:



高阶组件实现源码∶

  1. import React from 'react'
  2. import PropTypes from 'prop-types'
  3. // 高阶组件 contect
  4. export const connect = (mapStateToProps, mapDispatchToProps) => (WrappedComponent) => {
  5.     class Connect extends React.Component {
  6.         // 通过对context调用获取store
  7.         static contextTypes = {
  8.             store: PropTypes.object
  9.         }
  10.         constructor() {
  11.             super()
  12.             this.state = {
  13.                 allProps: {}
  14.             }
  15.         }
  16.         // 第一遍需初始化所有组件初始状态
  17.         componentWillMount() {
  18.             const store = this.context.store
  19.             this._updateProps()
  20.             store.subscribe(() => this._updateProps()); // 加入_updateProps()至store里的监听事件列表
  21.         }
  22.         // 执行action后更新props,使组件可以更新至最新状态(类似于setState)
  23.         _updateProps() {
  24.             const store = this.context.store;
  25.             let stateProps = mapStateToProps ?
  26.                 mapStateToProps(store.getState(), this.props) : {} // 防止 mapStateToProps 没有传入
  27.             let dispatchProps = mapDispatchToProps ?
  28.                 mapDispatchToProps(store.dispatch, this.props) : {
  29.                                     dispatch: store.dispatch
  30.                                 } // 防止 mapDispatchToProps 没有传入
  31.             this.setState({
  32.                 allProps: {
  33.                     ...stateProps,
  34.                     ...dispatchProps,
  35.                     ...this.props
  36.                 }
  37.             })
  38.         }
  39.         render() {
  40.             return <WrappedComponent {...this.state.allProps} />
  41.         }
  42.     }
  43.     return Connect
  44. }
复制代码

9. React组件的state和props有什么区别?


(1)props

props是一个从外部传进组件的参数,主要作为就是从父组件向子组件转达数据,它具有可读性和不变性,只能通过外部组件自动传入新的props来重新渲染子组件,否则子组件的props以及展现形式不会改变。

(2)state

state的主要作用是用于组件保存、控制以及修改自己的状态,它只能在constructor中初始化,它算是组件的私有属性,不可通过外部访问和修改,只能通过组件内部的this.setState来修改,修改state属性会导致组件的重新渲染。

(3)区别



10. React中的props为什么是只读的?


this.props是组件之间沟通的一个接口,原则上来讲,它只能从父组件流向子组件。React具有浓重的函数式编程的思想。

提到函数式编程就要提一个概念:纯函数。它有几个特点:



this.props就是汲取了纯函数的思想。props的不可以变性就保证的相同的输入,页面显示的内容是一样的,而且不会产生副作用

11. 在React中组件的props改变时更新组件的有哪些方法?


在一个组件传入的props更新时重新渲染该组件常用的方法是在componentWillReceiveProps中将新的props更新到组件的state中(这种state被成为派生状态(Derived State)),从而实现重新渲染。React 16.3中还引入了一个新的钩子函数getDerivedStateFromProps来专门实现这一需求。

(1)componentWillReceiveProps(已废弃)

在react的componentWillReceiveProps(nextProps)生命周期中,可以在子组件的render函数执行前,通过this.props获取旧的属性,通过nextProps获取新的props,对比两次props是否相同,从而更新子组件自己的state。

如许的好处是,可以将数据哀求放在这里进行执行,需要传的参数则从componentWillReceiveProps(nextProps)中获取。而不必将全部的哀求都放在父组件中。于是该哀求只会在该组件渲染时才会发出,从而减轻哀求负担。

(2)getDerivedStateFromProps(16.3引入)

这个生命周期函数是为了替代componentWillReceiveProps存在的,所以在需要使用componentWillReceiveProps时,就可以考虑使用getDerivedStateFromProps来进行替代。

两者的参数是不相同的,而getDerivedStateFromProps是一个静态函数,也就是这个函数不能通过this访问到class的属性,也并不保举直接访问属性。而是应该通过参数提供的nextProps以及prevState来进行判断,根据新传入的props来映射到state。

需要注意的是,如果props传入的内容不需要影响到你的state,那么就需要返回一个null,这个返回值是必须的,所以尽量将其写到函数的末尾:

  1. static getDerivedStateFromProps(nextProps, prevState) {
  2.     const {type} = nextProps;
  3.     // 当传入的type发生变化的时候,更新state
  4.     if (type !== prevState.type) {
  5.         return {
  6.             type,
  7.         };
  8.     }
  9.     // 否则,对于state不进行任何操作
  10.     return null;
  11. }
复制代码

12. React中怎么查验props?验证props的目标是什么?


React为我们提供了PropTypes以供验证使用。当我们向Props传入的数据无效(向Props传入的数据类型和验证的数据类型不符)就会在控制台发出告诫信息。它可以避免随着应用越来越复杂从而出现的题目。而且,它还可以让程序变得更易读。

  1. import PropTypes from 'prop-types';
  2. class Greeting extends React.Component {
  3.   render() {
  4.     return (
  5.       <h1>Hello, {this.props.name}</h1>
  6.     );
  7.   }
  8. }
  9. Greeting.propTypes = {
  10.   name: PropTypes.string
  11. };
复制代码

当然,如果项目汇中使用了TypeScript,那么就可以不用PropTypes来校验,而使用TypeScript界说接口来校验props。

三、生命周期


1. React的生命周期有哪些?


React 通常将组件生命周期分为三个阶段:






1)组件挂载阶段


挂载阶段组件被创建,然后组件实例插入到 DOM 中,完成组件的第一次渲染,该过程只会发生一次,在此阶段会依次调用以下这些方法:



(1)constructor


组件的构造函数,第一个被执行,若没有显式界说它,会有一个默认的构造函数,但是若显式界说了构造函数,我们必须在构造函数中执行 super(props),否则无法在构造函数中拿到this。

如果不初始化 state 或不进行方法绑定,则不需要为 React 组件实现构造函数Constructor

constructor中通常只做两件事:



  1. constructor(props) {
  2.   super(props);
  3.   // 不要在构造函数中调用 setState,可以直接给 state 设置初始值
  4.   this.state = { counter: 0 }
  5.   this.handleClick = this.handleClick.bind(this)
  6. }
复制代码

(2)getDerivedStateFromProps


  1. static getDerivedStateFromProps(props, state)
复制代码

这是个静态方法,所以不能在这个函数里使用 this,有两个参数 props 和 state,分别指吸收到的新参数和当前组件的 state 对象,这个函数会返回一个对象用来更新当前的 state 对象,如果不需要更新可以返回 null。

该函数会在装载时,吸收到新的 props 或者调用了 setState 和 forceUpdate 时被调用。如当吸收到新的属性想修改 state ,就可以使用。

  1. // 当 props.counter 变化时,赋值给 state
  2. class App extends React.Component {
  3.   constructor(props) {
  4.     super(props)
  5.     this.state = {
  6.       counter: 0
  7.     }
  8.   }
  9.   static getDerivedStateFromProps(props, state) {
  10.     if (props.counter !== state.counter) {
  11.       return {
  12.         counter: props.counter
  13.       }
  14.     }
  15.     return null
  16.   }
  17.   
  18.   handleClick = () => {
  19.     this.setState({
  20.       counter: this.state.counter + 1
  21.     })
  22.   }
  23.   render() {
  24.     return (
  25.       <div>
  26.         <h1 onClick={this.handleClick}>Hello, world!{this.state.counter}</h1>
  27.       </div>
  28.     )
  29.   }
  30. }
复制代码

如今可以显式传入 counter ,但是这里有个题目,如果想要通过点击实现 state.counter 的增长,但这时会发现值不会发生任何变化,一直保持 props 传进来的值。这是由于在 React 16.4^ 的版本中 setState 和 forceUpdate 也会触发这个生命周期,所以当组件内部 state 变化后,就会重新走这个方法,同时会把 state 值赋值为 props 的值。因此需要多加一个字段来记录之前的 props 值,如许就会办理上述题目。具体如下:

  1. // 这里只列出需要变化的地方
  2. class App extends React.Component {
  3.   constructor(props) {
  4.     super(props)
  5.     this.state = {
  6.       // 增加一个 preCounter 来记录之前的 props 传来的值
  7.       preCounter: 0,
  8.       counter: 0
  9.     }
  10.   }
  11.   static getDerivedStateFromProps(props, state) {
  12.     // 跟 state.preCounter 进行比较
  13.     if (props.counter !== state.preCounter) {
  14.       return {
  15.         counter: props.counter,
  16.         preCounter: props.counter
  17.       }
  18.     }
  19.     return null
  20.   }
  21.   handleClick = () => {
  22.     this.setState({
  23.       counter: this.state.counter + 1
  24.     })
  25.   }
  26.   render() {
  27.     return (
  28.       <div>
  29.         <h1 onClick={this.handleClick}>Hello, world!{this.state.counter}</h1>
  30.       </div>
  31.     )
  32.   }
  33. }
复制代码

(3)render


render是React 中最焦点的方法,一个组件中必须要有这个方法,它会根据状态 state 和属性 props 渲染组件。这个函数只做一件事,就是返回需要渲染的内容,所以不要在这个函数内做其他业务逻辑,通常调用该方法会返回以下类型中一个:



(4)componentDidMount()


componentDidMount()会在组件挂载后(插入 DOM 树中)立刻调。该阶段通常进行以下操作:



如果在 componentDidMount 中调用 setState ,就会触发一次额外的渲染,多调用了一次 render 函数,由于它是在浏览器刷新屏幕前执行的,所以用户对此是没有感知的,但是我应当避免如许使用,如许会带来一定的性能题目,尽量是在 constructor 中初始化 state 对象。

在组件装载之后,将计数数字变为1:

  1. class App extends React.Component  {
  2.   constructor(props) {
  3.     super(props)
  4.     this.state = {
  5.       counter: 0
  6.     }
  7.   }
  8.   componentDidMount () {
  9.     this.setState({
  10.       counter: 1
  11.     })
  12.   }
  13.   render ()  {
  14.     return (
  15.       <div className="counter">
  16.         counter值: { this.state.counter }
  17.       </div>
  18.     )
  19.   }
  20. }
复制代码

2)组件更新阶段


当组件的 props 改变了,或组件内部调用了 setState/forceUpdate,会触发更新重新渲染,这个过程大概会发生多次。这个阶段会依次调用下面这些方法:



(1)shouldComponentUpdate


  1. shouldComponentUpdate(nextProps, nextState)
复制代码

在说这个生命周期函数之前,来看两个题目:



  1. this.setState({number: this.state.number})
复制代码



第一个题目答案是 ,第二个题目如果是父组件重新渲染时,不管传入的 props 有没有变化,都会引起子组件的重新渲染。

那么有没有什么方法办理在这两个场景下不让组件重新渲染进而提升性能呢?这个时候 shouldComponentUpdate 登场了,这个生命周期函数是用来提升速度的,它是在重新渲染组件开始前触发的,默认返回 true,可以比较 this.props 和 nextProps ,this.state 和 nextState 值是否变化,来确认返回 true 或者 false。当返回 false 时,组件的更新过程制止,后续的 render、componentDidUpdate 也不会被调用。

注意:添加 shouldComponentUpdate 方法时,不建议使用深度相等查抄(如使用 JSON.stringify()),因为深比较效率很低,大概会比重新渲染组件效率还低。而且该方法维护比较困难,建议使用该方法会产生明显的性能提升时使用。

(2)getSnapshotBeforeUpdate


  1. getSnapshotBeforeUpdate(prevProps, prevState)
复制代码

这个方法在 render 之后,componentDidUpdate 之前调用,有两个参数 prevProps 和 prevState,表示更新之前的 props 和 state,这个函数必须要和 componentDidUpdate 一起使用,而且要有一个返回值,默认是 null,这个返回值作为第三个参数传给 componentDidUpdate。

(3)componentDidUpdate


componentDidUpdate() 会在更新后会被立刻调用,首次渲染不会执行此方法。 该阶段通常进行以下操作:



  1. componentDidUpdate(prevProps, prevState, snapshot){}
复制代码

该方法有三个参数:



3)组件卸载阶段


卸载阶段只有一个生命周期函数,componentWillUnmount() 会在组件卸载及烧毁之前直接调用。在此方法中执行须要的清算操作:



这个生命周期在一个组件被卸载和烧毁之前被调用,因此你不应该再这个方法中使用 setState,因为组件一旦被卸载,就不会再装载,也就不会重新渲染。

4)错误处理阶段


componentDidCatch(error, info),此生命周期在后代组件抛堕落误后被调用。 它吸收两个参数∶



React常见的生命周期如下:




React常见生命周期的过程大致如下:



React主要生命周期总结:


2. React 废弃了哪些生命周期?为什么?


被废弃的三个函数都是在render之前,因为fber的出现,很大概因为高优先级使命的出现而打断现有使命导致它们会被执行多次。另外的一个原因则是,React想束缚使用者,好的框架可以大概让人不得已写出轻易维护和扩展的代码,这一点又是从何谈起,可以从新增长以及即将废弃的生命周期分析入手

1) componentWillMount

首先这个函数的功能完全可以使用componentDidMount和 constructor来取代,异步获取的数据的情况上面已经阐明了,而如果抛去异步获取数据,其余的即是初始化而已,这些功能都可以在constructor中执行,除此之外,如果在 willMount 中订阅事件,但在服务端这并不会执行 willUnMount事件,也就是说服务端会导致内存走漏所以componentWilIMount完全可以不使用,但使用者有时候难免因为各 种各样的情况在 componentWilMount中做一些操作,那么React为了束缚开发者,干脆就抛掉了这个API

2) componentWillReceiveProps

在老版本的 React 中,如果组件自身的某个 state 跟其 props 密切相关的话,一直都没有一种很优雅的处理方式去更新 state,而是需要在 componentWilReceiveProps 中判断前后两个 props 是否相同,如果不同再将新的 props更新到相应的 state 上去。如许做一来会破坏 state 数据的单一数据源,导致组件状态变得不可预测,另一方面也会增长组件的重绘次数。类似的业务需求也有很多,如一个可以横向滑动的列表,当前高亮的 Tab 显然从属于列表自身的时,根据传入的某个值,直接定位到某个 Tab。为了办理这些题目,React引入了第一个新的生命周期:getDerivedStateFromProps。它有以下的优点∶



3) componentWillUpdate

与 componentWillReceiveProps 类似,许多开发者也会在 componentWillUpdate 中根据 props 的变化去触发一些回调 。 但不论是 componentWilReceiveProps 还 是 componentWilUpdate,都有大概在一次更新中被调用多次,也就是说写在这里的回调函数也有大概会被调用多次,这显然是不可取的。与 componentDidMount 类 似, componentDidUpdate 也不存在如许的题目,一次更新中 componentDidUpdate 只会被调用一次,所以将原先写在 componentWillUpdate 中 的 回 调 迁 移 至 componentDidUpdate 就可以办理这个题目。

另外一种情况则是需要获取DOM元素状态,但是由于在fber中,render可打断,大概在wilMount中获取到的元素状态很大概与实际需要的不同,这个通常可以使用第二个新增的生命函数的办理 getSnapshotBeforeUpdate(prevProps, prevState)

4) getSnapshotBeforeUpdate(prevProps, prevState)

返回的值作为componentDidUpdate的第三个参数。与willMount不同的是,getSnapshotBeforeUpdate会在最终确定的render执行之前执行,也就是能保证其获取到的元素状态与didUpdate中获取到的元素状态相同。官方参考代码:

  1. class ScrollingList extends React.Component {
  2.   constructor(props) {
  3.     super(props);
  4.     this.listRef = React.createRef();
  5.   }
  6.   getSnapshotBeforeUpdate(prevProps, prevState) {
  7.     // 我们是否在 list 中添加新的 items ?
  8.     // 捕获滚动位置以便我们稍后调整滚动位置。
  9.     if (prevProps.list.length < this.props.list.length) {
  10.       const list = this.listRef.current;
  11.       return list.scrollHeight - list.scrollTop;
  12.     }
  13.     return null;
  14.   }
  15.   componentDidUpdate(prevProps, prevState, snapshot) {
  16.     // 如果我们 snapshot 有值,说明我们刚刚添加了新的 items,
  17.     // 调整滚动位置使得这些新 items 不会将旧的 items 推出视图。
  18.     //(这里的 snapshot 是 getSnapshotBeforeUpdate 的返回值)
  19.     if (snapshot !== null) {
  20.       const list = this.listRef.current;
  21.       list.scrollTop = list.scrollHeight - snapshot;
  22.     }
  23.   }
  24.   render() {
  25.     return (
  26.       <div ref={this.listRef}>{/* ...contents... */}</div>
  27.     );
  28.   }
  29. }
复制代码

3. React 16.X 中 props 改变后在哪个生命周期中处理


在getDerivedStateFromProps中进行处理。

这个生命周期函数是为了替代componentWillReceiveProps存在的,所以在需要使用componentWillReceiveProps时,就可以考虑使用getDerivedStateFromProps来进行替代。

两者的参数是不相同的,而getDerivedStateFromProps是一个静态函数,也就是这个函数不能通过this访问到class的属性,也并不保举直接访问属性。而是应该通过参数提供的nextProps以及prevState来进行判断,根据新传入的props来映射到state。

需要注意的是,如果props传入的内容不需要影响到你的state,那么就需要返回一个null,这个返回值是必须的,所以尽量将其写到函数的末尾:

  1. static getDerivedStateFromProps(nextProps, prevState) {
  2.     const {type} = nextProps;
  3.     // 当传入的type发生变化的时候,更新state
  4.     if (type !== prevState.type) {
  5.         return {
  6.             type,
  7.         };
  8.     }
  9.     // 否则,对于state不进行任何操作
  10.     return null;
  11. }
复制代码

4. React 性能优化在哪个生命周期?它优化的原理是什么?


react的父级组件的render函数重新渲染会引起子组件的render方法的重新渲染。但是,有的时候子组件的担当父组件的数据没有变更。子组件render的执行会影响性能,这时就可以使用shouldComponentUpdate来办理这个题目。

使用方法如下:

  1. shouldComponentUpdate(nexrProps) {
  2.     if (this.props.num === nexrProps.num) {
  3.         return false
  4.     }
  5.     return true;
  6. }
复制代码

shouldComponentUpdate提供了两个参数nextProps和nextState,表示下一次props和一次state的值,当函数返回false时候,render()方法不执行,组件也就不会渲染,返回true时,组件照常重渲染。此方法就是拿当前props中值和下一次props中的值进行对比,数据相等时,返回false,反之返回true。

需要注意,在进行新旧对比的时候,是浅对比,也就是说如果比较的数据时引用数据类型,只要数据的引用的地址没变,即使内容变了,也会被判定为true。

面临这个题目,可以使用如下方法进行办理:

(1)使用setState改变数据之前,先采用ES6中assgin进行拷贝,但是assgin只深拷贝的数据的第一层,所以说不是最完美的办理办法:

  1. const o2 = Object.assign({},this.state.obj)
  2.     o2.student.count = '00000';
  3.     this.setState({
  4.         obj: o2,
  5.     })
复制代码

(2)使用JSON.parse(JSON.stringfy())进行深拷贝,但是遇到数据为undefined和函数时就会错。

  1. const o2 = JSON.parse(JSON.stringify(this.state.obj))
  2.     o2.student.count = '00000';
  3.     this.setState({
  4.         obj: o2,
  5.     })
复制代码

5. state 和 props 触发更新的生命周期分别有什么区别?


state 更新流程:




这个过程当中涉及的函数:


注意:此方法仅作为性能优化的方式而存在。不要计划依靠此方法来“制止”渲染,因为这大概会产生 bug。应该考虑使用内置的 PureComponent 组件,而不是手动编写 shouldComponentUpdate()


props 更新流程:




相对于 state 更新,props 更新后唯一的区别是增长了对 componentWillReceiveProps 的调用。关于 componentWillReceiveProps,需要知道这些事情:



6. React中发起网络哀求应该在哪个生命周期中进行?为什么?


对于异步哀求,最好放在componentDidMount中去操作,对于同步的状态改变,可以放在componentWillMount中,一样平常用的比较少。

如果认为在componentWillMount里发起哀求能提早获得结果,这种想法实在是错误的,通常componentWillMount比componentDidMount早不了多少微秒,网络上任何一点延迟,这一点差异都可忽略不计。

react的生命周期:constructor() -> componentWillMount() -> render() -> componentDidMount()

上面这些方法的调用是有序次的,由上而下依次调用。



总结:



7. React 16中新生命周期有哪些


关于 React16 开始应用的新生命周期:




可以看出,React16 自上而下地对生命周期做了另一种维度的解读:



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








四、组件通信


React组件间通信常见的几种情况:



1. 父子组件的通信方式?


父组件向子组件通信:父组件通过 props 向子组件转达需要的信息。

  1. // 子组件: Child
  2. const Child = props =>{
  3.   return <p>{props.name}</p>
  4. }
  5. // 父组件 Parent
  6. const Parent = ()=>{
  7.     return <Child name="react"></Child>
  8. }
复制代码

子组件向父组件通信:: props+回调的方式。

  1. // 子组件: Child
  2. const Child = props =>{
  3.   const cb = msg =>{
  4.       return ()=>{
  5.           props.callback(msg)
  6.       }
  7.   }
  8.   return (
  9.       <button onClick={cb("你好!")}>你好</button>
  10.   )
  11. }
  12. // 父组件 Parent
  13. class Parent extends Component {
  14.     callback(msg){
  15.         console.log(msg)
  16.     }
  17.     render(){
  18.         return <Child callback={this.callback.bind(this)}></Child>   
  19.     }
  20. }
复制代码

2. 跨级组件的通信方式?


父组件向子组件的子组件通信,向更深层子组件通信:



  1. // context方式实现跨级组件通信
  2. // Context 设计目的是为了共享那些对于一个组件树而言是“全局”的数据
  3. const BatteryContext = createContext();
  4. //  子组件的子组件
  5. class GrandChild extends Component {
  6.     render(){
  7.         return (
  8.             <BatteryContext.Consumer>
  9.                 {
  10.                     color => <h1 style={{"color":color}}>我是红色的:{color}</h1>
  11.                 }
  12.             </BatteryContext.Consumer>
  13.         )
  14.     }
  15. }
  16. //  子组件
  17. const Child = () =>{
  18.     return (
  19.         <GrandChild/>
  20.     )
  21. }
  22. // 父组件
  23. class Parent extends Component {
  24.       state = {
  25.           color:"red"
  26.       }
  27.       render(){
  28.           const {color} = this.state
  29.           return (
  30.           <BatteryContext.Provider value={color}>
  31.               <Child></Child>
  32.           </BatteryContext.Provider>
  33.           )
  34.       }
  35. }
复制代码

3. 非嵌套关系组件的通信方式?


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



4. 如何办理 props 层级过深的题目




5. 组件通信的方式有哪些




五、路由


1. React-Router的实现原理是什么?


客户端路由实现的思想:






react-router 实现的思想:



2. 如何配置 React-Router 实现路由切换


(1)使用  组件

路由匹配是通过比较  的 path 属性和当前地址的 pathname 来实现的。当一个  匹配乐成时,它将渲染其内容,当它不匹配时就会渲染 null。没有路径的  将始终被匹配。

  1. // when location = { pathname: '/about' }
  2. <Route path='/about' component={About}/> // renders <About/>
  3. <Route path='/contact' component={Contact}/> // renders null
  4. <Route component={Always}/> // renders <Always/>
复制代码

(2)联合使用  组件和  组件

用于将  分组。

  1. <Switch>
  2.     <Route exact path="/" component={Home} />
  3.     <Route path="/about" component={About} />
  4.     <Route path="/contact" component={Contact} />
  5. </Switch>
复制代码

不是分组  所必须的,但他通常很有用。 一个  会遍历其全部的子 元素,并仅渲染与当前地址匹配的第一个元素。

(3)使用 、 、 组件

)。
  1. <Link to="/">Home</Link>   
  2. // <a href='/'>Home</a>
复制代码

是一种特殊类型的 当它的 to属性与当前地址匹配时,可以将其界说为"活跃的"。

  1. // location = { pathname: '/react' }
  2. <NavLink to="/react" activeClassName="hurray">
  3.     React
  4. </NavLink>
  5. // <a href='/react' className='hurray'>React</a>
复制代码

当我们想逼迫导航时,可以渲染一个,当一个渲染时,它将使用它的to属性进行定向。
3. React-Router怎么设置重定向?



使用组件实现路由的重定向:

  1. <Switch>
  2.   <Redirect from='/users/:id' to='/users/profile/:id'/>
  3.   <Route path='/users/profile/:id' component={Profile}/>
  4. </Switch>
复制代码

当哀求 /users/:id 被重定向去 '/users/profile/:id':


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



从最终渲染的 DOM 来看,这两者都是链接,都是 标签,区别是∶
是react-router 里实现路由跳转的链接,一样平常配合 使用,react-router接管了其默认的链接跳转举动,区别于传统的页面跳转, 的“跳转”举动只会触发相匹配的对应的页面内容更新,而不会刷新整个页面。 做了3件事情:


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

  1. let domArr = document.getElementsByTagName('a')
  2. [...domArr].forEach(item=>{
  3.     item.addEventListener('click',function () {
  4.         location.href = this.href
  5.     })
  6. })
复制代码
5. React-Router如何获取URL的参数和汗青对象?



(1)获取URL的参数



路由配置还是普通的配置,如:'admin',传参方式如:'admin?id='1111''。通过this.props.location.search获取url获取到一个字符串'?id='1111'

可以用url,qs,querystring,浏览器提供的api URLSearchParams对象或者自己封装的方法去解析出id的值。



路由需要配置成动态路由:如path='/admin/:id',传参方式,如'admin/111'。通过this.props.match.params.id 取得url中的动态路由id部分的值,除此之外还可以通过useParams(Hooks)来获取



传参方式如:在Link组件的to属性中可以转达对象{pathname:'/admin',query:'111',state:'111'};。通过this.props.location.state或this.props.location.query来获取即可,转达的参数可以是对象、数组等,但是存在缺点就是只要刷新页面,参数就会丢失。

(2)获取汗青对象



  1. import { useHistory } from "react-router-dom";
  2. let history = useHistory();
复制代码

2.使用this.props.history获取汗青对象

  1. let history = this.props.history;
复制代码
6. React-Router 4怎样在路由变化时重新渲染同一个组件?



当路由变化时,即组件的props发生了变化,会调用componentWillReceiveProps等生命周期钩子。那需要做的只是: 当路由改变时,根据路由,也去哀求数据:

  1. class NewsList extends Component {
  2.   componentDidMount () {
  3.      this.fetchData(this.props.location);
  4.   }
  5.   
  6.   fetchData(location) {
  7.     const type = location.pathname.replace('/', '') || 'top'
  8.     this.props.dispatch(fetchListData(type))
  9.   }
  10.   componentWillReceiveProps(nextProps) {
  11.      if (nextProps.location.pathname != this.props.location.pathname) {
  12.          this.fetchData(nextProps.location);
  13.      }
  14.   }
  15.   render () {
  16.     ...
  17.   }
  18. }
复制代码

利用生命周期componentWillReceiveProps,进行重新render的预处理操作。
7. React-Router的路由有几种模式?



React-Router 支持使用 hash(对应 HashRouter)和 browser(对应 BrowserRouter) 两种路由规则, react-router-dom 提供了 BrowserRouter 和 HashRouter 两个组件来实现应用的 UI 和 URL 同步:



(1)BrowserRouter

它使用 HTML5 提供的 history API(pushState、replaceState 和 popstate 事件)来保持 UI 和 URL 的同步。由此可以看出,BrowserRouter 是使用 HTML 5 的 history API 来控制路由跳转的:

  1. <BrowserRouter
  2.     basename={string}
  3.     forceRefresh={bool}
  4.     getUserConfirmation={func}
  5.     keyLength={number}
  6. />
复制代码

此中的属性如下:



  1. <BrowserRouter basename="/calendar">
  2.     <Link to="/today" />
  3. </BrowserRouter>
复制代码

等同于

  1. <a href="/calendar/today" />
复制代码



  1. // 这是默认的确认函数
  2. const getConfirmation = (message, callback) => {
  3.   const allowTransition = window.confirm(message);
  4.   callback(allowTransition);
  5. }
  6. <BrowserRouter getUserConfirmation={getConfirmation} />
复制代码

需要配合<rompt> 一起使用。



(2)HashRouter

使用 URL 的 hash 部分(即 window.location.hash)来保持 UI 和 URL 的同步。由此可以看出,HashRouter 是通过 URL 的 hash 属性来控制路由跳转的:

  1. <HashRouter
  2.     basename={string}
  3.     getUserConfirmation={func}
  4.     hashType={string}  
  5. />
复制代码

此中的参数如下



8. React-Router 4的Switch有什么用?



Switch 通常被用来包裹 Route,用于渲染与路径匹配的第一个子 <Route> 或 <Redirect>,它内里不能放其他元素。

假如不加 <Switch> :

  1. import { Route } from 'react-router-dom'
  2. <Route path="/" component={Home}></Route>
  3. <Route path="/login" component={Login}></Route>
复制代码

Route 组件的 path 属性用于匹配路径,因为需要匹配 / 到 Home,匹配 /login 到 Login,所以需要两个 Route,但是不能这么写。如许写的话,当 URL 的 path 为 “/login” 时,<Route path="/" />和<Route path="/login" /> 都会被匹配,因此页面会展示 Home 和 Login 两个组件。这时就需要借助 <Switch> 来做到只显示一个匹配组件:

  1. import { Switch, Route} from 'react-router-dom'
  2.    
  3. <Switch>
  4.     <Route path="/" component={Home}></Route>
  5.     <Route path="/login" component={Login}></Route>
  6. </Switch>
复制代码

此时,再访问 “/login” 路径时,却只显示了 Home 组件。这是就用到了exact属性,它的作用就是精确匹配路径,经常与<Switch> 联合使用。只有当 URL 和该 <Route> 的 path 属性完全一致的情况下才能匹配上:

  1. import { Switch, Route} from 'react-router-dom'
  2.    
  3. <Switch>
  4.    <Route exact path="/" component={Home}></Route>
  5.    <Route exact path="/login" component={Login}></Route>
  6. </Switch>
复制代码
六、Redux


1. 对 Redux 的理解,主要办理什么题目



React是视图层框架。Redux是一个用来管理数据状态和UI状态的JavaScript应用工具。随着JavaScript单页应用(SPA)开发日趋复杂, JavaScript需要管理比任何时候都要多的state(状态), Redux就是降低管理难度的。(Redux支持React、Angular、jQuery甚至纯JavaScript)。

在 React 中,UI 以组件的形式来搭建,组件之间可以嵌套组合。但 React 中组件间通信的数据流是单向的,顶层组件可以通过 props 属性向下层组件转达数据,而下层组件不能向上层组件转达数据,兄弟组件之间同样不能。如许简单的单向数据流支撑起了 React 中的数据可控性。

当项目越来越大的时候,管理数据的事件或回调函数将越来越多,也将越来越不好管理。管理不断变化的 state 非常困难。如果一个 model 的变化会引起另一个 model 变化,那么当 view 变化时,就大概引起对应 model 以及另一个 model 的变化,依次地,大概会引起另一个 view 的变化。直至你搞不清楚到底发生了什么。state 在什么时候,由于什么原因,如何变化已然不受控制。 当体系变得错综复杂的时候,想重现题目或者添加新功能就会变得举步维艰。如果这还不够糟糕,考虑一些来自前端开发范畴的新需求,如更新调优、服务端渲染、路由跳转前哀求数据等。state 的管理在大项目中相称复杂。

Redux 提供了一个叫 store 的统一仓储库,组件通过 dispatch 将 state 直接传入store,不用通过其他的组件。而且组件通过 subscribe 从 store获取到 state 的改变。使用了 Redux,全部的组件都可以从 store 中获取到所需的 state,他们也能从store 获取到 state 的改变。这比组件之间相互转达数据清晰明朗的多。

主要办理的题目:

单纯的Redux只是一个状态机,是没有UI出现的,react- redux作用是将Redux的状态机和React的UI出现绑定在一起,当你dispatch action改变state的时候,会自动更新页面。
2. Redux 原理及工作流程



(1)原理

Redux源码主要分为以下几个模块文件



  1. const actionTypes = {
  2.     ADD: 'ADD',
  3.     CHANGEINFO: 'CHANGEINFO',
  4. }
  5. const initState = {
  6.     info: '初始化',
  7. }
  8. export default function initReducer(state=initState, action) {
  9.     switch(action.type) {
  10.         case actionTypes.CHANGEINFO:
  11.             return {
  12.                 ...state,
  13.                 info: action.preload.info || '',
  14.             }
  15.         default:
  16.             return { ...state };
  17.     }
  18. }
  19. export default function createStore(reducer, initialState, middleFunc) {
  20.     if (initialState && typeof initialState === 'function') {
  21.         middleFunc = initialState;
  22.         initialState = undefined;
  23.     }
  24.     let currentState = initialState;
  25.     const listeners = [];
  26.     if (middleFunc && typeof middleFunc === 'function') {
  27.         // 封装dispatch
  28.         return middleFunc(createStore)(reducer, initialState);
  29.     }
  30.     const getState = () => {
  31.         return currentState;
  32.     }
  33.     const dispatch = (action) => {
  34.         currentState = reducer(currentState, action);
  35.         listeners.forEach(listener => {
  36.             listener();
  37.         })
  38.     }
  39.     const subscribe = (listener) => {
  40.         listeners.push(listener);
  41.     }
  42.     return {
  43.         getState,
  44.         dispatch,
  45.         subscribe
  46.     }
  47. }
复制代码

(2)工作流程



通俗点表明:



以 store 为焦点,可以把它看成数据存储中心,但是他要更改数据的时候不能直接修改,数据修改更新的角色由Reducers来担任,store只做存储,中间人,当Reducers的更新完成以后会通过store的订阅来通知react component,组件把新的状态重新获取渲染,组件中也能自动发送action,创建action后这个动作是不会执行的,所以要dispatch这个action,让store通过reducers去做更新React Component 就是react的每个组件。
3. Redux 中异步的哀求怎么处理



可以在 componentDidmount 中直接进⾏哀求⽆须借助redux。但是在⼀定规模的项⽬中,上述⽅法很难进⾏异步流的管理,通常情况下我们会借助redux的异步中间件进⾏异步处理。redux异步流中间件实在有很多,当下主流的异步中间件有两种redux-thunk、redux-saga。

(1)使用react-thunk中间件

redux-thunk优点:



redux-thunk缺陷:



使用步调:



  1. import {createStore, applyMiddleware, compose} from 'redux';
  2. import reducer from './reducer';
  3. import thunk from 'redux-thunk'
  4. // 设置调试工具
  5. const composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ ? window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__({}) : compose;
  6. // 设置中间件
  7. const enhancer = composeEnhancers(
  8.   applyMiddleware(thunk)
  9. );
  10. const store = createStore(reducer, enhancer);
  11. export default store;
复制代码



  1. /**
  2.   发送get请求,并生成相应action,更新store的函数
  3.   @param url {string} 请求地址
  4.   @param func {function} 真正需要生成的action对应的actionCreator
  5.   @return {function}
  6. */
  7. // dispatch为自动接收的store.dispatch函数
  8. export const getHttpAction = (url, func) => (dispatch) => {
  9.     axios.get(url).then(function(res){
  10.         const action = func(res.data)
  11.         dispatch(action)
  12.     })
  13. }
复制代码



  1. componentDidMount(){
  2.     var action = getHttpAction('/getData', getInitTodoItemAction)
  3.     // 发送函数类型的action时,该action的函数体会自动执行
  4.     store.dispatch(action)
  5. }
复制代码

(2)使用redux-saga中间件

redux-saga优点:



redux-saga缺陷:



redux-saga可以捕捉action,然后执行一个函数,那么可以把异步代码放在这个函数中,使用步调如下:



  1. import {createStore, applyMiddleware, compose} from 'redux';
  2. import reducer from './reducer';
  3. import createSagaMiddleware from 'redux-saga'
  4. import TodoListSaga from './sagas'
  5. const composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ ? window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__({}) : compose;
  6. const sagaMiddleware = createSagaMiddleware()
  7. const enhancer = composeEnhancers(
  8.   applyMiddleware(sagaMiddleware)
  9. );
  10. const store = createStore(reducer, enhancer);
  11. sagaMiddleware.run(TodoListSaga)
  12. export default store;
复制代码



  1. import {takeEvery, put} from 'redux-saga/effects'
  2. import {initTodoList} from './actionCreator'
  3. import {GET_INIT_ITEM} from './actionTypes'
  4. import axios from 'axios'
  5. function* func(){
  6.     try{
  7.         // 可以获取异步返回数据
  8.         const res = yield axios.get('/getData')
  9.         const action = initTodoList(res.data)
  10.         // 将action发送到reducer
  11.         yield put(action)
  12.     }catch(e){
  13.         console.log('网络请求失败')
  14.     }
  15. }
  16. function* mySaga(){
  17.     // 自动捕获GET_INIT_ITEM类型的action,并执行func
  18.     yield takeEvery(GET_INIT_ITEM, func)
  19. }
  20. export default mySaga
复制代码



  1. componentDidMount(){
  2.   const action = getInitTodoItemAction()
  3.   store.dispatch(action)
  4. }
复制代码
4. Redux 怎么实现属性转达,先容下原理



react-redux 数据传输∶ view-->action-->reducer-->store-->view。看下点击事件的数据是如何通过redux传到view上:



代码示例∶

  1. import React from 'react';
  2. import ReactDOM from 'react-dom';
  3. import { createStore } from 'redux';
  4. import { Provider, connect } from 'react-redux';
  5. class App extends React.Component{
  6.     render(){
  7.         let { text, click, clickR } = this.props;
  8.         return(
  9.             <div>
  10.                 <div>数据:已有人{text}</div>
  11.                 <div onClick={click}>加人</div>
  12.                 <div onClick={clickR}>减人</div>
  13.             </div>
  14.         )
  15.     }
  16. }
  17. const initialState = {
  18.     text:5
  19. }
  20. const reducer = function(state,action){
  21.     switch(action.type){
  22.         case 'ADD':
  23.             return {text:state.text+1}
  24.         case 'REMOVE':
  25.             return {text:state.text-1}
  26.         default:
  27.             return initialState;
  28.     }
  29. }
  30. let ADD = {
  31.     type:'ADD'
  32. }
  33. let Remove = {
  34.     type:'REMOVE'
  35. }
  36. const store = createStore(reducer);
  37. let mapStateToProps = function (state){
  38.     return{
  39.         text:state.text
  40.     }
  41. }
  42. let mapDispatchToProps = function(dispatch){
  43.     return{
  44.         click:()=>dispatch(ADD),
  45.         clickR:()=>dispatch(Remove)
  46.     }
  47. }
  48. const App1 = connect(mapStateToProps,mapDispatchToProps)(App);
  49. ReactDOM.render(
  50.     <Provider store = {store}>
  51.         <App1></App1>
  52.     </Provider>,document.getElementById('root')
  53. )
复制代码
5. Redux 中间件是什么?担当几个参数?柯里化函数两端的参数具体是什么?



Redux 的中间件提供的是位于 action 被发起之后,到达 reducer 之前的扩展点,换而言之,原本 view -→> action -> reducer -> store 的数据流加上中间件后变成了 view -> action -> middleware -> reducer -> store ,在这一环节可以做一些"副作用"的操作,如异步哀求、打印日志等。

applyMiddleware源码:

  1. export default function applyMiddleware(...middlewares) {
  2.     return createStore => (...args) => {
  3.         // 利用传入的createStore和reducer和创建一个store
  4.         const store = createStore(...args)
  5.         let dispatch = () => {
  6.             throw new Error()
  7.         }
  8.         const middlewareAPI = {
  9.             getState: store.getState,
  10.             dispatch: (...args) => dispatch(...args)
  11.         }
  12.         // 让每个 middleware 带着 middlewareAPI 这个参数分别执行一遍
  13.         const chain = middlewares.map(middleware => middleware(middlewareAPI))
  14.         // 接着 compose 将 chain 中的所有匿名函数,组装成一个新的函数,即新的 dispatch
  15.         dispatch = compose(...chain)(store.dispatch)
  16.         return {
  17.             ...store,
  18.             dispatch
  19.         }
  20.     }
  21. }
复制代码

从applyMiddleware中可以看出∶


6. Redux 哀求中间件如那边理并发



使用redux-Saga

redux-saga是一个管理redux应用异步操作的中间件,用于取代 redux-thunk 的。它通过创建 Sagas 将全部异步操作逻辑存放在一个地方进行集中处理,以此将react中的同步操作与异步操作区分开来,以便于后期的管理与维护。 redux-saga如那边理并发:



可以让多个 saga 使命并行被 fork 执行。

  1. import {
  2.     fork,
  3.     take
  4. } from "redux-saga/effects"
  5. const takeEvery = (pattern, saga, ...args) => fork(function*() {
  6.     while (true) {
  7.         const action = yield take(pattern)
  8.         yield fork(saga, ...args.concat(action))
  9.     }
  10. })
复制代码



takeLatest 不允许多个 saga 使命并行地执行。一旦吸收到新的发起的 action,它就会取消前面全部 fork 过的使命(如果这些使命还在执行的话)。

在处理 AJAX 哀求的时候,如果只盼望获取末了谁人哀求的响应, takeLatest 就会非常有用。

  1. import {
  2.     cancel,
  3.     fork,
  4.     take
  5. } from "redux-saga/effects"
  6. const takeLatest = (pattern, saga, ...args) => fork(function*() {
  7.     let lastTask
  8.     while (true) {
  9.         const action = yield take(pattern)
  10.         if (lastTask) {
  11.             yield cancel(lastTask) // 如果任务已经结束,则 cancel 为空操作
  12.         }
  13.         lastTask = yield fork(saga, ...args.concat(action))
  14.     }
  15. })
复制代码
7. Redux 状态管理器和变量挂载到 window 中有什么区别



两者都是存储数据以供后期使用。但是Redux状态更改可回溯——Time travel,数据多了的时候可以很清晰的知道改动在那里发生,完备的提供了一套状态管理模式。

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

管理不断变化的 state 非常困难。如果一个 model 的变化会引起另一个 model 变化,那么当 view 变化时,就大概引起对应 model 以及另一个model 的变化,依次地,大概会引起另一个 view 的变化。直至你搞不清楚到底发生了什么。state 在什么时候,由于什么原因,如何变化已然不受控制。 当体系变得错综复杂的时候,想重现题目或者添加新功能就会变得举步维艰。

如果这还不够糟糕,考虑一些来自前端开发范畴的新需求,如更新调优、服务端渲染、路由跳转前哀求数据等等。前端开发者正在经受前所未有的复杂性,难道就这么放弃了吗?当然不是。

这里的复杂性很大程度上来自于:我们总是将两个难以理清的概念肴杂在一起:变化和异步。 可以称它们为曼妥思和可乐。如果把二者分开,能做的很好,但混到一起,就变得一团糟。一些库如 React 视图在视图层禁止异步和直接操作 DOM来办理这个题目。美中不足的是,React 仍然把处理 state 中数据的题目留给了你。Redux就是为了帮你办理这个题目。
8. mobox 和 redux 有什么区别?



(1)共同点



(2)区别

Redux更多的是遵照Flux模式的一种实现,是一个 JavaScript库,它关注点主要是以下几方面∶



    o type∶ action 类型;

    o payload∶ 负载数据;



    o 维护应用状态并支持访问状态(getState());

    o 支持监听action的分发,更新状态(dispatch(action));

    o 支持订阅store的变更(subscribe(listener));



Mobx是一个透明函数响应式编程的状态管理库,它使得状态管理简单可伸缩∶



对比总结:


9. Redux 和 Vuex 有什么区别,它们的共同思想



(1)Redux 和 Vuex区别



通俗点理解就是,vuex 弱化 dispatch,通过commit进行 store状态的一次更变;取消了action概念,不必传入特定的 action形式进行指定变更;弱化reducer,基于commit参数直接对数据进行转变,使得框架更加简易;

(2)共同思想



本质上∶ redux与vuex都是对mvvm思想的服务,将数据从视图中抽离的一种方案。
10. Redux 中间件是怎么拿到store 和 action? 然后怎么处理?



redux中间件本质就是一个函数柯里化。redux applyMiddleware Api 源码中每个middleware 担当2个参数, Store 的getState 函数和dispatch 函数,分别获得store和action,最终返回一个函数。该函数会被传入 next 的下一个 middleware 的 dispatch 方法,并返回一个吸收 action 的新函数,这个函数可以直接调用 next(action),或者在其他需要的时刻调用,甚至根本不去调用它。调用链中末了一个 middleware 会担当真实的 store的 dispatch 方法作为 next 参数,并借此结束调用链。所以,middleware 的函数署名是({ getState,dispatch })=> next => action。
11. 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及其子组件的重新渲染
七、Hooks


1. 对 React Hook 的理解,它的实现原理是什么



React-Hooks 是 React 团队在 React 组件开发实践中,逐渐认知到的一个改进点,这背后实在涉及对类组件函数组两种组件形式的思考和侧重。

(1)类组件:所谓类组件,就是基于 ES6 Class 这种写法,通过继承 React.Component 得来的 React 组件。以下是一个类组件:

  1. class DemoClass extends React.Component {
  2.   state = {
  3.     text: ""
  4.   };
  5.   componentDidMount() {
  6.     //...
  7.   }
  8.   changeText = (newText) => {
  9.     this.setState({
  10.       text: newText
  11.     });
  12.   };
  13.   render() {
  14.     return (
  15.       <div className="demoClass">
  16.         <p>{this.state.text}</p>
  17.         <button onClick={this.changeText}>修改</button>
  18.       </div>
  19.     );
  20.   }
  21. }
复制代码

可以看出,React 类组件内部预置了相称多的“现成的东西”等着我们去调理/定制,state 和生命周期就是这些“现成东西”中的典型。要想得到这些东西,难度也不大,只需要继承一个 React.Component 即可。

当然,这也是类组件的一个未便,它太繁杂了,对于办理许多题目来说,编写一个类组件实在是一个过于复杂的姿势。复杂的姿势一定带来高昂的理解成本,这也是我们所不想看到的。除此之外,由于开发者编写的逻辑在封装后是和组件粘在一起的,这就使得类组件内部的逻辑难以实现拆分和复用。

(2)函数组件:函数组件就是以函数的形态存在的 React 组件。早期并没有 React-Hooks,函数组件内部无法界说和维护 state,因此它还有一个别名叫“无状态组件”。以下是一个函数组件:

  1. function DemoFunction(props) {
  2.   const { text } = props
  3.   return (
  4.     <div className="demoFunction">
  5.       <p>{`函数组件接收的内容:[${text}]`}</p>
  6.     </div>
  7.   );
  8. }
复制代码

相比于类组件,函数组件肉眼可见的特质自然包括轻量、机动、易于组织和维护、较低的学习成本等。

通过对比,从形态上可以对两种组件做区分,它们之间的区别如下:



除此之外,还有一些其他的不同。通过上面的区别,我们不能说谁好谁坏,它们各有自己的优势。在 React-Hooks 出现之前,类组件的本领边界明显强于函数组件。

实际上,类组件和函数组件之间,是面向对象和函数式编程这两套不同的设计思想之间的差异。而函数组件更加契合 React 框架的设计理念:




React 组件本身的定位就是函数,一个输入数据、输出 UI 的函数。作为开发者,我们编写的是声明式的代码,而 React 框架的主要工作,就是及时地把声明式的代码转换为命令式的 DOM 操作,把数据层面的描述映射到用户可见的 UI 变化中去。这就意味着从原则上来讲,React 的数据应该总是牢牢地和渲染绑定在一起的,而类组件做不到这一点。函数组件就真正地将数据和渲染绑定到了一起。函数组件是一个更加匹配其设计理念、也更有利于逻辑拆分与重用的组件表达形式。

为了能让开发者更好的的去编写函数式组件。于是,React-Hooks 便应运而生。

React-Hooks 是一套可以大概使函数组件更强盛、更机动的“钩子”。

函数组件比起类组件少了很多东西,好比生命周期、对 state 的管理等。这就给函数组件的使用带来了非常多的局限性,导致我们并不能使用函数这种形式,写出一个真正的全功能的组件。而React-Hooks 的出现,就是为了资助函数组件补齐这些(相对于类组件来说)缺失的本领。

如果说函数组件是一台轻巧的快艇,那么 React-Hooks 就是一个内容丰富的零部件箱。“重装战舰”所预置的那些设备,这个箱子里根本全都有,同时它还不逼迫你全都要,而是允许你自由地选择和使用你需要的那些本领,然后将这些本领以 Hook(钩子)的形式“钩”进你的组件里,从而定制出一个最得当你的“专属战舰”。
2. 为什么 useState 要使用数组而不是对象



useState 的用法:

  1. const [count, setCount] = useState(0)
复制代码

可以看到 useState 返回的是一个数组,那么为什么是返回数组而不是返回对象呢?

这里用到了解构赋值,所以先来看一下ES6 的解构赋值:
数组的解构赋值



  1. const foo = [1, 2, 3];
  2. const [one, two, three] = foo;
  3. console.log(one);   // 1
  4. console.log(two);   // 2
  5. console.log(three); // 3
复制代码
对象的解构赋值



  1. const user = {
  2.   id: 888,
  3.   name: "xiaoxin"
  4. };
  5. const { id, name } = user;
  6. console.log(id);    // 888
  7. console.log(name);  // "xiaoxin"
复制代码

看完这两个例子,答案应该就出来了:



下面来看看如果 useState 返回对象的情况:

  1. // 第一次使用
  2. const { state, setState } = useState(false);
  3. // 第二次使用
  4. const { state: counter, setState: setCounter } = useState(0)
复制代码

这里可以看到,返回对象的使用方式还是挺麻烦的,更何况实际项目中会使用的更频繁。

总结:useState 返回的是 array 而不是 object 的原因就是为了降低使用的复杂度,返回数组的话可以直接根据顺序解构,而返回对象的话要想使用多次就需要界说别名了。
3. React Hooks 办理了哪些题目?



React Hooks 主要办理了以下题目:

(1)在组件之间复用状态逻辑很难

React 没有提供将可复用性举动“附加”到组件的途径(比方,把组件连接到 store)办理此类题目可以使用 render props 和 高阶组件。但是这类方案需要重新组织组件结构,这大概会很麻烦,而且会使代码难以理解。由 providers,consumers,高阶组件,render props 等其他抽象层组成的组件会形成“嵌套地狱”。尽管可以在 DevTools 过滤掉它们,但这阐明了一个更深层次的题目:React 需要为共享状态逻辑提供更好的原生途径。

可以使用 Hook 从组件中提取状态逻辑,使得这些逻辑可以单独测试并复用。Hook 使我们在无需修改组件结构的情况下复用状态逻辑。 这使得在组件间或社区内共享 Hook 变得更便捷。

(2)复杂组件变得难以理解

在组件中,每个生命周期常常包罗一些不相关的逻辑。比方,组件常常在 componentDidMount 和 componentDidUpdate 中获取数据。但是,同一个 componentDidMount 中大概也包罗很多其它的逻辑,如设置事件监听,而之后需在 componentWillUnmount 中打扫。相互关联且需要对照修改的代码被进行了拆分,而完全不相关的代码却在同一个方法中组合在一起。云云很轻易产生 bug,而且导致逻辑不一致。

在多数情况下,不大概将组件拆分为更小的粒度,因为状态逻辑无处不在。这也给测试带来了一定挑战。同时,这也是很多人将 React 与状态管理库联合使用的原因之一。但是,这每每会引入了很多抽象概念,需要你在不同的文件之间来回切换,使得复用变得更加困难。

为了办理这个题目,Hook 将组件中相互关联的部分拆分成更小的函数(好比设置订阅或哀求数据),而并非逼迫按照生命周期划分。你还可以使用 reducer 来管理组件的内部状态,使其更加可预测。

(3)难以理解的 class

除了代码复用和代码管理会遇到困难外,class 是学习 React 的一大屏障。我们必须去理解 JavaScript 中 this 的工作方式,这与其他语言存在巨大差异。还不能忘记绑定事件处理器。没有稳定的语法提案,这些代码非常冗余。大家可以很好地理解 props,state 和自顶向下的数据流,但对 class 却一筹莫展。即便在有经验的 React 开发者之间,对于函数组件与 class 组件的差异也存在分歧,甚至还要区分两种组件的使用场景。

为了办理这些题目,Hook 使你在非 class 的情况下可以使用更多的 React 特性。 从概念上讲,React 组件一直更像是函数。而 Hook 则拥抱了函数,同时也没有捐躯 React 的精神原则。Hook 提供了题目标办理方案,无需学习复杂的函数式或响应式编程技能
4. React Hook 的使用限制有哪些?



React Hooks 的限制主要有两条:



那为什么会有如许的限制呢?Hooks 的设计初衷是为了改进 React 组件的开发模式。在旧有的开发模式下遇到了三个题目。



这三个题目在一定程度上拦阻了 React 的后续发展,所以为了办理这三个题目,Hooks 基于函数组件开始设计。然而第三个题目决定了 Hooks 只支持函数组件。

那为什么不要在循环、条件或嵌套函数中调用 Hook 呢?因为 Hooks 的设计是基于数组实现。在调用时按顺序加入数组中,如果使用循环、条件或嵌套函数很有大概导致数组取值错位,执行错误的 Hook。当然,实质上 React 的源码里不是数组,是链表。

这些限制会在编码上造成一定程度的心智负担,新手大概会写错,为了避免如许的情况,可以引入 ESLint 的 Hooks 查抄插件进行预防。
5. useEffect 与 useLayoutEffect 的区别



(1)共同点



(2)不同点



在未来的趋势上,两个 API 是会长期共存的,暂时没有删减合并的计划,需要开发者根据场景去自行选择。React 团队的建议非常实用,如果实在分不清,先用 useEffect,一样平常题目不大;如果页面有非常,再直接替换为 useLayoutEffect 即可。
6. React Hooks在平时开发中需要注意的题目和原因



(1)不要在循环,条件或嵌套函数中调用Hook,必须始终在 React函数的顶层使用Hook

这是因为React需要利用调用顺序来正确更新相应的状态,以及调用相应的钩子函数。一旦在循环或条件分支语句中调用Hook,就轻易导致调用顺序的不一致性,从而产生难以预推测的后果。

(2)使用useState时候,使用push,pop,splice等直接更改数组对象的坑

使用push直接更改数组无法获取到新值,应该采用析构方式,但是在class内里不会有这个题目。代码示例:

  1. function Indicatorfilter() {
  2.   let [num,setNums] = useState([0,1,2,3])
  3.   const test = () => {
  4.     // 这里坑是直接采用push去更新num
  5.     // setNums(num)是无法更新num的
  6.     // 必须使用num = [...num ,1]
  7.     num.push(1)
  8.     // num = [...num ,1]
  9.     setNums(num)
  10.   }
  11. return (
  12.     <div className='filter'>
  13.       <div onClick={test}>测试</div>
  14.         <div>
  15.           {num.map((item,index) => (
  16.               <div key={index}>{item}</div>
  17.           ))}
  18.       </div>
  19.     </div>
  20.   )
  21. }
  22. class Indicatorfilter extends React.Component<any,any>{
  23.   constructor(props:any){
  24.       super(props)
  25.       this.state = {
  26.           nums:[1,2,3]
  27.       }
  28.       this.test = this.test.bind(this)
  29.   }
  30.   test(){
  31.       // class采用同样的方式是没有问题的
  32.       this.state.nums.push(1)
  33.       this.setState({
  34.           nums: this.state.nums
  35.       })
  36.   }
  37.   render(){
  38.       let {nums} = this.state
  39.       return(
  40.           <div>
  41.               <div onClick={this.test}>测试</div>
  42.                   <div>
  43.                       {nums.map((item:any,index:number) => (
  44.                           <div key={index}>{item}</div>
  45.                       ))}
  46.                   </div>
  47.           </div>
  48.       )
  49.   }
  50. }
复制代码

(3)useState设置状态的时候,只有第一次生效,后期需要更新状态,必须通过useEffect

TableDeail是一个公共组件,在调用它的父组件内里,我们通过set改变columns的值,以为转达给TableDeail 的 columns是最新的值,所以tabColumn每次也是最新的值,但是实际tabColumn是最开始的值,不会随着columns的更新而更新:

  1. const TableDeail = ({
  2.     columns,
  3. }:TableData) => {
  4.     const [tabColumn, setTabColumn] = useState(columns)
  5. }
  6. // 正确的做法是通过useEffect改变这个值
  7. const TableDeail = ({
  8.     columns,
  9. }:TableData) => {
  10.     const [tabColumn, setTabColumn] = useState(columns)
  11.     useEffect(() =>{setTabColumn(columns)},[columns])
  12. }
复制代码

(4)善用useCallback

父组件转达给子组件事件句柄时,如果我们没有任何参数变更大概会选用useMemo。但是每一次父组件渲染子组件即使没变化也会跟着渲染一次。

(5)不要滥用useContext

可以使用基于 useContext 封装的状态管理工具。
7. React Hooks 和生命周期的关系?



函数组件 的本质是函数,没有 state 的概念的,因此不存在生命周期一说,仅仅是一个 render 函数而已。

但是引入 Hooks 之后就变得不同了,它能让组件在不使用 class 的情况下拥有 state,所以就有了生命周期的概念,所谓的生命周期实在就是 useState、 useEffect() 和 useLayoutEffect() 。

即:Hooks 组件(使用了Hooks的函数组件)有生命周期,而函数组件(未使用Hooks的函数组件)是没有生命周期的

下面是具体的 class 与 Hooks 的生命周期对应关系



  1. const [num, UpdateNum] = useState(0)
复制代码



  1. function ScrollView({row}) {
  2.   let [isScrollingDown, setIsScrollingDown] = useState(false);
  3.   let [prevRow, setPrevRow] = useState(null);
  4.   if (row !== prevRow) {
  5.     // Row 自上次渲染以来发生过改变。更新 isScrollingDown。
  6.     setIsScrollingDown(prevRow !== null && row > prevRow);
  7.     setPrevRow(row);
  8.   }
  9.   return `Scrolling down: ${isScrollingDown}`;
  10. }
复制代码

React 会立刻退出第一次渲染并用更新后的 state 重新运行组件以避免耗费太多性能。



  1. const Button = React.memo((props) => {
  2.   // 具体的组件
  3. });
复制代码

注意:**React.memo** **等效于** ``**PureComponent**,它只浅比较 props。这里也可以使用 useMemo 优化每一个节点。



  1. // componentDidMount
  2. useEffect(()=>{
  3.   // 需要在 componentDidMount 执行的内容
  4. }, [])
  5. useEffect(() => {
  6.   // 在 componentDidMount,以及 count 更改时 componentDidUpdate 执行的内容
  7.   document.title = `You clicked ${count} times`;
  8.   return () => {
  9.     // 需要在 count 更改时 componentDidUpdate(先于 document.title = ... 执行,遵守先清理后更新)
  10.     // 以及 componentWillUnmount 执行的内容      
  11.   } // 当函数中 Cleanup 函数会按照在代码中定义的顺序先后执行,与函数本身的特性无关
  12. }, [count]); // 仅在 count 更改时更新
复制代码

请记得 React 会等待浏览器完成画面渲染之后才会延迟调用 ,因此会使得额外操作很方便



  1. // componentDidMount/componentWillUnmount
  2. useEffect(()=>{
  3.   // 需要在 componentDidMount 执行的内容
  4.   return function cleanup() {
  5.     // 需要在 componentWillUnmount 执行的内容      
  6.   }
  7. }, [])
复制代码


class 组件Hooks 组件constructoruseStategetDerivedStateFromPropsuseState 内里 update 函数shouldComponentUpdateuseMemorender函数本身componentDidMountuseEffectcomponentDidUpdateuseEffectcomponentWillUnmountuseEffect 内里返回的函数componentDidCatch无getDerivedStateFromError无
八、虚拟DOM


1. 对虚拟 DOM 的理解?虚拟 DOM 主要做了什么?虚拟 DOM 本身是什么?



从本质上来说,Virtual Dom是一个JavaScript对象,通过对象的方式来表示DOM结构。将页面的状态抽象为JS对象的形式,配合不同的渲染工具,使跨平台渲染成为大概。通过事务处理机制,将多次DOM修改的结果一次性的更新到页面上,从而有效的淘汰页面渲染的次数,淘汰修改DOM的重绘重排次数,进步渲染性能。

虚拟DOM是对DOM的抽象,这个对象是更加轻量级的对DOM的描述。它设计的最初目标,就是更好的跨平台,好比node.js就没有DOM,如果想实现SSR,那么一个方式就是借助虚拟dom,因为虚拟dom本身是js对象。 在代码渲染到页面之前,vue或者react会把代码转换成一个对象(虚拟DOM)。以对象的形式来描述真实dom结构,最终渲染到页面。在每次数据发生变化前,虚拟dom都会缓存一份,变化之时,如今的虚拟dom会与缓存的虚拟dom进行比较。在vue或者react内部封装了diff算法,通过这个算法来进行比较,渲染时修改改变的变化,原先没有发生改变的通过原先的数据进行渲染。

另外现代前端框架的一个根本要求就是无须手动操作DOM,一方面是因为手动操作DOM无法保证程序性能,多人协作的项目中如果review不严格,大概会有开发者写出性能较低的代码,另一方面更重要的是省略手动DOM操作可以大大进步开发效率。

为什么要用 Virtual DOM:

(1)保证性能下限,在不进行手动优化的情况下,提供过得去的性能

下面临比一下修改DOM时真实DOM操作和Virtual DOM的过程,来看一下它们重排重绘的性能消耗∶



Virtual DOM的更新DOM的预备工作耗费更多的时间,也就是JS层面,相比于更多的DOM操作它的消费是极其便宜的。尤雨溪在社区论坛中说道∶ 框架给你的保证是,你不需要手动优化的情况下,我依然可以给你提供过得去的性能。

(2)跨平台

Virtual DOM本质上是JavaScript的对象,它可以很方便的跨平台操作,好比服务端渲染、uniapp等。
2. React diff 算法的原理是什么?



实际上,diff 算法探究的就是虚拟 DOM 树发生变化后,生成 DOM 树更新补丁的方式。它通过对比新旧两株虚拟 DOM 树的变更差异,将更新补丁作用于真实 DOM,以最小成本完成视图更新。




具体的流程如下:






一个简单的例子:

  1. import React from 'react'
  2. export default class ExampleComponent extends React.Component {
  3.   render() {
  4.     if(this.props.isVisible) {
  5.        return <div className="visible">visbile</div>;
  6.     }
  7.      return <div className="hidden">hidden</div>;
  8.   }
  9. }
复制代码

这里,首先假定 ExampleComponent 可见,然后再改变它的状态,让它不可见 。映射为真实的 DOM 操作是如许的,React 会创建一个 div 节点。

  1. <div class="visible">visbile</div>
复制代码

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

diff算法可以总结为三个计谋,分别从树、组件及元素三个层面进行复杂度的优化:

计谋一:忽略节点跨层级操作场景,提升比对效率。(基于树进行对比)

这一计谋需要进行树比对,即对树进行分层比较。树比对的处理手法黑白常“暴力”的,即两棵树只对同一层次的节点进行比较,如果发现节点已经不存在了,则该节点及其子节点会被完全删撤除,不会用于进一步的比较,这就提升了比对效率。

计谋二:如果组件的 class 一致,则默认为相似的树结构,否则默认为不同的树结构。(基于组件进行对比)

在组件比对的过程中:



只要父组件类型不同,就会被重新渲染。这也就是为什么 shouldComponentUpdate、PureComponent 及 React.memo 可以进步性能的原因。

计谋三:同一层级的子节点,可以通过标记 key 的方式进行列表对比。(基于节点进行对比)

元素比对主要发生在同层级中,通过标记节点操作生成补丁。节点操作包罗了插入、移动、删除等。此中节点重新排序同时涉及插入、移动、删除三个操作,所以效率消耗最大,此时计谋三起到了至关重要的作用。通过标记 key 的方式,React 可以直接移动 DOM 节点,降低内耗。
3. React key 是干嘛用的 为什么要加?key 主要是办理哪一类题目标



Keys 是 React 用于追踪哪些列表中元素被修改、被添加或者被移除的辅助标识。在开发过程中,我们需要保证某个元素的 key 在其同级元素中具有唯一性。

在 React Diff 算法中 React 会借助元素的 Key 值来判断该元素是新近创建的还是被移动而来的元素,从而淘汰不须要的元素重渲染此外,React 还需要借助 Key 值来判断元素与本地状态的关联关系。

注意事项:


4. 虚拟 DOM 的引入与直接操作原生 DOM 相比,哪一个效率更高,为什么



虚拟DOM相对原生的DOM不一定是效率更高,如果只修改一个按钮的文案,那么虚拟 DOM 的操作无论如何都不大概比真实的 DOM 操作更快。在首次渲染大量DOM时,由于多了一层虚拟DOM的盘算,虚拟DOM也会比innerHTML插入慢。它能保证性能下限,在真实DOM操作的时候进行针对性的优化时,还是更快的。所以要根据具体的场景进行探究。

在整个 DOM 操作的演化过程中,实在主要矛盾并不在于性能,而在于开发者写得爽不爽,在于研发体验/研发效率。虚拟 DOM 不是别的,正是前端开发们为了寻求更好的研发体验和研发效率而创造出来的高阶产物。虚拟 DOM 并不一定会带来更好的性能,React 官方也从来没有把虚拟 DOM 作为性能层面的卖点对外输出过。虚拟 DOM 的优越之处在于,它可以大概在提供更爽、更高效的研发模式(也就是函数式的 UI 编程方式)的同时,仍然保持一个还不错的性能。
5. React 与 Vue 的 diff 算法有何不同?



diff 算法是指生成更新补丁的方式,主要应用于虚拟 DOM 树变化后,更新真实 DOM。所以 diff 算法一定存在如许一个过程:触发更新 → 生成补丁 → 应用补丁。

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



以上是经典的 React diff 算法内容。自 React 16 起,引入了 Fiber 架构。为了使整个更新过程可随时暂停规复,节点与树分别采用了 FiberNode 与 FiberTree 进行重构。fiberNode 使用了双链表的结构,可以直接找到兄弟节点与子节点。整个更新过程由 current 与 workInProgress 两株树双缓冲完成。workInProgress 更新完成后,再通过修改 current 相关指针指向新节点。

Vue 的整体 diff 计谋与 React 对齐,固然缺乏时间切片本领,但这并不意味着 Vue 的性能更差,因为在 Vue 3 初期引入过,后期因为收益不高移撤除了。除了高帧率动画,在 Vue 中其他的场景几乎都可以使用防抖和节省去进步响应性能。
九、其他


1. React组件定名保举的方式是哪个?



通过引用而不是使用来定名组件displayName。

使用displayName定名组件:

  1. export default React.createClass({
  2.   displayName: 'TodoApp',
  3.   // ...
  4. })
复制代码

React保举的方法:

  1. export default class TodoApp extends React.Component {
  2.   // ...
  3. }
复制代码
2. react 最新版本办理了什么题目,增长了哪些东西



React 16.x的三大新特性 Time Slicing、Suspense、 hooks



(1)React16.8

加入hooks,让React函数式组件更加机动,hooks之前,React存在很多题目:



hooks很好的办理了上述题目,hooks提供了很多方法



(2)React16.9



(3)React16.13.0


3. react 实现一个全局的 dialog



  1. import React, { Component } from 'react';
  2. import { is, fromJS } from 'immutable';
  3. import ReactDOM from 'react-dom';
  4. import ReactCSSTransitionGroup from 'react-addons-css-transition-group';
  5. import './dialog.css';
  6. let defaultState = {
  7.   alertStatus:false,
  8.   alertTip:"提示",
  9.   closeDialog:function(){},
  10.   childs:''
  11. }
  12. class Dialog extends Component{
  13.   state = {
  14.     ...defaultState
  15.   };
  16.   // css动画组件设置为目标组件
  17.   FirstChild = props => {
  18.     const childrenArray = React.Children.toArray(props.children);
  19.     return childrenArray[0] || null;
  20.   }
  21.   //打开弹窗
  22.   open =(options)=>{
  23.     options = options || {};
  24.     options.alertStatus = true;
  25.     var props = options.props || {};
  26.     var childs = this.renderChildren(props,options.childrens) || '';
  27.     console.log(childs);
  28.     this.setState({
  29.       ...defaultState,
  30.       ...options,
  31.       childs
  32.     })
  33.   }
  34.   //关闭弹窗
  35.   close(){
  36.     this.state.closeDialog();
  37.     this.setState({
  38.       ...defaultState
  39.     })
  40.   }
  41.   renderChildren(props,childrens) {
  42.     //遍历所有子组件
  43.     var childs = [];
  44.     childrens = childrens || [];
  45.     var ps = {
  46.         ...props,  //给子组件绑定props
  47.         _close:this.close  //给子组件也绑定一个关闭弹窗的事件   
  48.        };
  49.     childrens.forEach((currentItem,index) => {
  50.         childs.push(React.createElement(
  51.             currentItem,
  52.             {
  53.                 ...ps,
  54.                 key:index
  55.             }
  56.         ));
  57.     })
  58.     return childs;
  59.   }
  60.   shouldComponentUpdate(nextProps, nextState){
  61.     return !is(fromJS(this.props), fromJS(nextProps)) || !is(fromJS(this.state), fromJS(nextState))
  62.   }
  63.    
  64.   render(){
  65.     return (
  66.       <ReactCSSTransitionGroup
  67.         component={this.FirstChild}
  68.         transitionName='hide'
  69.         transitionEnterTimeout={300}
  70.         transitionLeaveTimeout={300}>
  71.         <div className="dialog-con" style={this.state.alertStatus? {display:'block'}:{display:'none'}}>
  72.             {this.state.childs}
  73.         </div>
  74.       </ReactCSSTransitionGroup>
  75.     );
  76.   }
  77. }
  78. let div = document.createElement('div');
  79. let props = {
  80.    
  81. };
  82. document.body.appendChild(div);
  83. let Box = ReactD
复制代码

子类:

  1. //子类jsx
  2. import React, { Component } from 'react';
  3. class Child extends Component {
  4.     constructor(props){
  5.         super(props);
  6.         this.state = {date: new Date()};
  7.   }
  8.   showValue=()=>{
  9.     this.props.showValue && this.props.showValue()
  10.   }
  11.   render() {
  12.     return (
  13.       <div className="Child">
  14.         <div className="content">
  15.            Child
  16.            <button onClick={this.showValue}>调用父的方法</button>
  17.         </div>
  18.       </div>
  19.     );
  20.   }
  21. }
  22. export default Child;
复制代码

css:

  1. .dialog-con{
  2.     position: fixed;
  3.     top: 0;
  4.     left: 0;
  5.     width: 100%;
  6.     height: 100%;
  7.     background: rgba(0, 0, 0, 0.3);
  8. }
复制代码
4. React 数据持久化有什么实践吗?



封装数据持久化组件:

  1. 】let storage={
  2.     // 增加
  3.     set(key, value){
  4.         localStorage.setItem(key, JSON.stringify(value));
  5.     },
  6.     // 获取
  7.     get(key){
  8.         return JSON.parse(localStorage.getItem(key));
  9.     },
  10.     // 删除
  11.     remove(key){
  12.         localStorage.removeItem(key);
  13.     }
  14. };
  15. export default Storage;
复制代码

在React项目中,通过redux存储全局数据时,会有一个题目,如果用户刷新了网页,那么通过redux存储的全局数据就会被全部清空,好比登录信息等。这时就会有全局数据持久化存储的需求。首先想到的就是localStorage,localStorage是没有时间限制的数据存储,可以通过它来实现数据的持久化存储。

但是在已经使用redux来管理和存储全局数据的基础上,再去使用localStorage来读写数据,如许不但是工作量巨大,还轻易堕落。那么有没有联合redux来到达持久数据存储功能的框架呢?当然,它就是redux-persist。redux-persist会将redux的store中的数据缓存到浏览器的localStorage中。其使用步调如下:

(1)首先要安装redux-persist:

  1. npm i redux-persist
复制代码

(2)对于reducer和action的处理不变,只需修改store的生成代码,修改如下:

  1. import {createStore} from 'redux'
  2. import reducers from '../reducers/index'
  3. import {persistStore, persistReducer} from 'redux-persist';
  4. import storage from 'redux-persist/lib/storage';
  5. import autoMergeLevel2 from 'redux-persist/lib/stateReconciler/autoMergeLevel2';
  6. const persistConfig = {
  7.     key: 'root',
  8.     storage: storage,
  9.     stateReconciler: autoMergeLevel2 // 查看 'Merge Process' 部分的具体情况
  10. };
  11. const myPersistReducer = persistReducer(persistConfig, reducers)
  12. const store = createStore(myPersistReducer)
  13. export const persistor = persistStore(store)
  14. export default store
复制代码

(3)在index.js中,将PersistGate标签作为网页内容的父标签:

  1. import React from 'react';
  2. import ReactDOM from 'react-dom';
  3. import {Provider} from 'react-redux'
  4. import store from './redux/store/store'
  5. import {persistor} from './redux/store/store'
  6. import {PersistGate} from 'redux-persist/lib/integration/react';
  7. ReactDOM.render(<Provider store={store}>
  8.             <PersistGate loading={null} persistor={persistor}>
  9.                 {/*网页内容*/}
  10.             </PersistGate>
  11.         </Provider>, document.getElementById('root'));
复制代码

这就完成了通过redux-persist实现React持久化本地数据存储的简单应用。
5. 对 React 和 Vue 的理解,它们的异同



相似之处:



不同之处:

1)数据流

Vue默认支持数据双向绑定,而React一直提倡单向数据流

2)虚拟DOM

Vue2.x开始引入"Virtual DOM",消除了和React在这方面的差异,但是在具体的细节还是有各自的特点。



3)组件化

React与Vue最大的不同是模板的编写。



具体来讲:React中render函数是支持闭包特性的,所以我们import的组件在render中可以直接调用。但是在Vue中,由于模板中使用的数据都必须挂在 this 上进行一次中转,所以 import 完组件之后,还需要在 components 中再声明下。

4)监听数据变化的实现原理不同



5)高阶组件

react可以通过高阶组件(Higher Order Components-- HOC)来扩展,而vue需要通过mixins来扩展。

原因高阶组件就是高阶函数,而React的组件本身就是纯粹的函数,所以高阶函数对React来说轻而易举。相反Vue.js使用HTML模板创建视图组件,这时模板无法有效的编译,因此Vue不采用HOC来实现。

6)构建工具

两者都有自己的构建工具



7)跨平台


6. 可以使用TypeScript写React应用吗?怎么操作?



(1)如果还未创建 Create React App 项目



  1. npx create-react-app demo --typescript
复制代码

(2)如果已经创建了 Create React App 项目,需要将 typescript 引入到已有项目中



  1. npm install --save typescript @types/node @types/react @types/react-dom @types/jest
复制代码


7. React 设计思路,它的理念是什么?



(1)编写简单直观的代码

React最大的价值不是高性能的虚拟DOM、封装的事件机制、服务器端渲染,而是声明式的直观的编码方式。react文档第一条就是声明式,React 使创建交互式 UI 变得轻而易举。为应用的每一个状态设计简便的视图,当数据改变时 React 能有效地更新并正确地渲染组件。 以声明式编写 UI,可以让代码更加可靠,且方便调试。

(2)简化可复用的组件

React框架内里使用了简化的组件模子,但更彻底地使用了组件化的概念。React将整个UI上的每一个功能模块界说成组件,然后将小的组件通过组合或者嵌套的方式构成更大的组件。React的组件具有如下的特性∶



(3) Virtual DOM

真实页面临应一个 DOM 树。在传统页面的开发模式中,每次需要更新页面时,都要手动操作 DOM 来进行更新。 DOM 操作非常昂贵。在前端开发中,性能消耗最大的就是 DOM 操作,而且这部分代码会让整体项目标代码变得难 以维护。React 把真实 DOM 树转换成 JavaScript 对象树,也就是 Virtual DOM,每次数据更新后,重新盘算 Virtual DOM,并和上一次生成的 Virtual DOM 做对比,对发生变化的部分做批量更新。React 也提供了直观的 shouldComponentUpdate 生命周期回调,来淘汰数据变化后不须要的 Virtual DOM 对比过程,以保证性能。

(4)函数式编程

React 把过去不断重复构建 UI 的过程抽象成了组件,且在给定参数的情况下约定渲染对应的 UI 界面。React 能充分利用很多函数式方法去淘汰冗余代码。此外,由于它本身就是简单函数,所以易于测试。

(5)一次学习,随处编写

无论如今正在使用什么技能栈,都可以随时引入 React来开发新特性,而不需要重写现有代码。

React 还可以使用 Node 进行服务器渲染,或使用 React Native 开发原生移动应用。因为 React 组件可以映射为对应的原生控件。在输出的时候,是输出 Web DOM,还是 Android 控件,还是 iOS 控件,就由平台本身决定了。所以,react很方便和其他平台集成
8. React中props.children和React.Children的区别



在React中,当涉及组件嵌套,在父组件中使用props.children把全部子组件显示出来。如下:

  1. function ParentComponent(props){
  2.     return (
  3.         <div>
  4.             {props.children}
  5.         </div>
  6.     )
  7. }
复制代码

如果想把父组件中的属性传给全部的子组件,需要使用React.Children方法。

好比,把几个Radio组合起来,合成一个RadioGroup,这就要求全部的Radio具有同样的name属性值。可以如许:把Radio看做子组件,RadioGroup看做父组件,name的属性值在RadioGroup这个父组件中设置。

首先是子组件:

  1. //子组件
  2. function RadioOption(props) {
  3.   return (
  4.     <label>
  5.       <input type="radio" value={props.value} name={props.name} />
  6.       {props.label}
  7.     </label>
  8.   )
  9. }
复制代码

然后是父组件,不但需要把它全部的子组件显示出来,还需要为每个子组件赋上name属性和值:

  1. //父组件用,props是指父组件的props
  2. function renderChildren(props) {
  3.    
  4.   //遍历所有子组件
  5.   return React.Children.map(props.children, child => {
  6.     if (child.type === RadioOption)
  7.       return React.cloneElement(child, {
  8.         //把父组件的props.name赋值给每个子组件
  9.         name: props.name
  10.       })
  11.     else
  12.       return child
  13.   })
  14. }
  15. //父组件
  16. function RadioGroup(props) {
  17.   return (
  18.     <div>
  19.       {renderChildren(props)}
  20.     </div>
  21.   )
  22. }
  23. function App() {
  24.   return (
  25.     <RadioGroup name="hello">
  26.       <RadioOption label="选项一" value="1" />
  27.       <RadioOption label="选项二" value="2" />
  28.       <RadioOption label="选项三" value="3" />
  29.     </RadioGroup>
  30.   )
  31. }
  32. export default App;
复制代码

以上,React.Children.map让我们对父组件的全部子组件又更机动的控制。
9. React的状态提升是什么?使用场景有哪些?



React的状态提升就是用户对子组件操作,子组件不改变自己的状态,通过自己的props把这个操作改变的数据转达给父组件,改变父组件的状态,从而改变受父组件控制的全部子组件的状态,这也是React单项数据流的特性决定的。官方的原话是:共享 state(状态) 是通过将其移动到需要它的组件的最接近的共同祖先组件来实现的。 这被称为“状态提升(Lifting State Up)”。

概括来说就是将多个组件需要共享的状态提升到它们最近的父组件上在父组件上改变这个状态然后通过props分发给子组件。

一个简单的例子,父组件中有两个input子组件,如果想在第一个输入框输入数据,来改变第二个输入框的值,这就需要用到状态提升。

  1. class Father extends React.Component {
  2.     constructor(props) {
  3.         super(props)
  4.         this.state = {
  5.             Value1: '',
  6.             Value2: ''
  7.         }
  8.     }
  9.     value1Change(aa) {
  10.         this.setState({
  11.             Value1: aa
  12.         })
  13.     }
  14.     value2Change(bb) {
  15.         this.setState({
  16.             Value2: bb
  17.         })
  18.     }
  19.     render() {
  20.         return (
  21.             <div style={{ padding: "100px" }}>
  22.                 <Child1 value1={this.state.Value1} onvalue1Change={this.value1Change.bind(this)} />
  23.                 <br />
  24.                 <Child2 value2={this.state.Value1} />
  25.             </div>
  26.         )
  27.     }
  28. }
  29. class Child1 extends React.Component {
  30.     constructor(props) {
  31.         super(props)
  32.     }
  33.     changeValue(e) {
  34.         this.props.onvalue1Change(e.target.value)
  35.     }
  36.     render() {
  37.         return (
  38.             <input value={this.props.Value1} onChange={this.changeValue.bind(this)} />
  39.         )
  40.     }
  41. }
  42. class Child2 extends React.Component {
  43.     constructor(props) {
  44.         super(props)
  45.     }
  46.     render() {
  47.         return (
  48.             <input value={this.props.value2} />
  49.         )
  50.     }
  51. }
  52. ReactDOM.render(
  53.     <Father />,
  54.     document.getElementById('root')
  55. )
复制代码
10. React中constructor和getInitialState的区别?



两者都是用来初始化state的。前者是ES6中的语法,后者是ES5中的语法,新版本的React中已经废弃了该方法。

getInitialState是ES5中的方法,如果使用createClass方法创建一个Component组件,可以自动调用它的getInitialState方法来获取初始化的State对象,

  1. var APP = React.creatClass ({
  2.   getInitialState() {
  3.     return {
  4.         userName: 'hi',
  5.         userId: 0
  6.      };
  7.  }
  8. })
复制代码

React在ES6的实现中去掉了getInitialState这个hook函数,规定state在constructor中实现,如下:

  1. Class App extends React.Component{
  2.     constructor(props){
  3.       super(props);
  4.       this.state={};
  5.     }
  6.   }
复制代码
11. React的严格模式如何使用,有什么用处?



StrictMode 是一个用来突出显示应用程序中潜在题目标工具。与 Fragment 一样,StrictMode 不会渲染任何可见的 UI。它为其后代元素触发额外的查抄和告诫。

可以为应用程序的任何部分启用严格模式。比方:

  1. import React from 'react';
  2. function ExampleApplication() {
  3.   return (
  4.     <div>
  5.       <Header />
  6.       <React.StrictMode>        
  7.         <div>
  8.           <ComponentOne />
  9.           <ComponentTwo />
  10.         </div>
  11.       </React.StrictMode>      
  12.       <Footer />
  13.     </div>
  14.   );
  15. }
复制代码

在上述的示例中,会对 Header 和 Footer 组件运行严格模式查抄。但是,ComponentOne 和 ComponentTwo 以及它们的全部后代元素都将进行查抄。

StrictMode 目前有助于:


12. 在React中遍历的方法有哪些?



(1)遍历数组:map && forEach

  1. import React from 'react';
  2. class App extends React.Component {
  3.   render() {
  4.     let arr = ['a', 'b', 'c', 'd'];
  5.     return (
  6.       <ul>
  7.         {
  8.           arr.map((item, index) => {
  9.             return <li key={index}>{item}</li>
  10.           })
  11.         }
  12.       </ul>
  13.     )
  14.   }
  15. }
  16. class App extends React.Component {
  17.   render() {
  18.     let arr = ['a', 'b', 'c', 'd'];
  19.     return (
  20.       <ul>
  21.         {
  22.           arr.forEach((item, index) => {
  23.             return <li key={index}>{item}</li>
  24.           })
  25.         }
  26.       </ul>
  27.     )
  28.   }
  29. }
复制代码

(2)遍历对象:map && for in

  1. class App extends React.Component {
  2.   render() {
  3.     let obj = {
  4.       a: 1,
  5.       b: 2,
  6.       c: 3
  7.     }
  8.     return (
  9.       <ul>
  10.         {
  11.           (() => {
  12.             let domArr = [];
  13.             for(const key in obj) {
  14.               if(obj.hasOwnProperty(key)) {
  15.                 const value = obj[key]
  16.                 domArr.push(<li key={key}>{value}</li>)
  17.               }
  18.             }
  19.             return domArr;
  20.           })()
  21.         }
  22.       </ul>
  23.     )
  24.   }
  25. }
  26. // Object.entries() 把对象转换成数组
  27. class App extends React.Component {
  28.   render() {
  29.     let obj = {
  30.       a: 1,
  31.       b: 2,
  32.       c: 3
  33.     }
  34.     return (
  35.       <ul>
  36.         {
  37.           Object.entries(obj).map(([key, value], index) => {   // item是一个数组,把item解构,写法是[key, value]
  38.             return <li key={key}>{value}</li>
  39.           })
  40.         }
  41.       </ul>
  42.     )
  43.   }
  44. }
复制代码
13. 在React中页面重新加载时怎样保留数据?



这个题目就设计到了数据持久化,主要的实现方式有以下几种:


14. 同时引用这三个库react.js、react-dom.js和babel.js它们都有什么作用?




15. React必须使用JSX吗?



React 并不逼迫要求使用 JSX。当不想在构建环境中配置有关 JSX 编译时,不在 React 中使用 JSX 会更加方便。

每个 JSX 元素只是调用 React.createElement(component, props, ...children) 的语法糖。因此,使用 JSX 可以完成的任何事情都可以通过纯 JavaScript 完成。

比方,用 JSX 编写的代码:

  1. class Hello extends React.Component {
  2.   render() {
  3.     return <div>Hello {this.props.toWhat}</div>;
  4.   }
  5. }
  6. ReactDOM.render(
  7.   <Hello toWhat="World" />,
  8.   document.getElementById('root')
  9. );
复制代码

可以编写为不使用 JSX 的代码:

  1. class Hello extends React.Component {
  2.   render() {
  3.     return React.createElement('div', null, `Hello ${this.props.toWhat}`);
  4.   }
  5. }
  6. ReactDOM.render(
  7.   React.createElement(Hello, {toWhat: 'World'}, null),
  8.   document.getElementById('root')
  9. );
复制代码
16. 为什么使用jsx的组件中没有看到使用react却需要引入react?



本质上来说JSX是React.createElement(component, props, ...children)方法的语法糖。在React 17之前,如果使用了JSX,实在就是在使用React, babel 会把组件转换为 CreateElement 形式。在React 17之后,就不再需要引入,因为 babel 已经可以帮我们自动引入react。
17. 在React中怎么使用async/await?



async/await是ES7标准中的新特性。如果是使用React官方的脚手架创建的项目,就可以直接使用。如果是在自己搭建的webpack配置的项目中使用,大概会遇到 regeneratorRuntime is not defined 的非常错误。那么我们就需要引入babel,并在babel中配置使用async/await。可以利用babel的 transform-async-to-module-method 插件来转换其成为浏览器支持的语法,固然没有性能的提升,但对于代码编写体验要更好。
18. React.Children.map和js的map有什么区别?



JavaScript中的map不会对为null或者undefined的数据进行处理,而React.Children.map中的map可以处理React.Children为null或者undefined的情况。
19. 对React SSR的理解



服务端渲染是数据与模版组成的html,即 HTML = 数据 + 模版。将组件或页面通过服务器生成html字符串,再发送到浏览器,末了将静态标记"肴杂"为客户端上完全交互的应用程序。页面没使用服务渲染,当哀求页面时,返回的body里为空,之后执行js将html结构注入到body里,联合css显示出来;

SSR的优势:



1)更利于SEO

不同爬虫工作原理类似,只会爬取源码,不会执行网站的任何脚本使用了React或者其它MVVM框架之后,页面大多数DOM元素都是在客户端根据js动态生成,可供爬虫抓取分析的内容大大淘汰。另外,浏览器爬虫不会等待我们的数据完成之后再去抓取页面数据。服务端渲染返回给客户端的是已经获取了异步数据并执行JavaScript脚本的最终HTML,网络爬中就可以抓取到完备页面的信息。

2)更利于首屏渲染

首屏的渲染是node发送过来的html字符串,并不依赖于js文件了,这就会使用户更快的看到页面的内容。尤其是针对大型单页应用,打包后文件体积比较大,普通客户端渲染加载全部所需文件时间较长,首页就会有一个很长的白屏等待时间。

SSR的局限:

1)服务端压力较大

本来是通过客户端完成渲染,如今统一到服务端node服务去做。尤其是高并发访问的情况,会大量占用服务端CPU资源;

2)开发条件受限

在服务端渲染中,只会执行到componentDidMount之前的生命周期钩子,因此项目引用的第三方的库也不可用其它生命周期钩子,这对引用库的选择产生了很大的限制;

3)学习成本相对较高

除了对webpack、MVVM框架要认识,还需要把握node、 Koa2等相关技能。相对于客户端渲染,项目构建、部署过程更加复杂。

时间耗时比较:

1)数据哀求

由服务端哀求首屏数据,而不是客户端哀求首屏数据,这是"快"的一个主要原因。服务端在内网进行哀求,数据响应速度快。客户端在不同网络环境进行数据哀求,且外网http哀求开销大,导致时间差











2)html渲染

服务端渲染是先向后端服务器哀求数据,然后生成完备首屏 html返回给浏览器;而客户端渲染是等js代码下载、加载、解析完成后再哀求数据渲染,等待的过程页面是什么都没有的,就是用户看到的白屏。就是服务端渲染不需要等待js代码下载完成并哀求数据,就可以返回一个已有完备数据的首屏页面。










20. 为什么 React 要用 JSX?



JSX 是一个 JavaScript 的语法扩展,或者说是一个类似于 XML 的 ECMAScript 语法扩展。它本身没有太多的语法界说,也不盼望引入更多的标准。

实在 React 本身并不逼迫使用 JSX。在没有 JSX 的时候,React 实现一个组件依赖于使用 React.createElement 函数。代码如下:

  1. class Hello extends React.Component {
  2.   render() {
  3.     return React.createElement(
  4.         'div',
  5.         null,
  6.         `Hello ${this.props.toWhat}`
  7.       );
  8.   }
  9. }
  10. ReactDOM.render(
  11.   React.createElement(Hello, {toWhat: 'World'}, null),
  12.   document.getElementById('root')
  13. );
复制代码

而 JSX 更像是一种语法糖,通过类似 XML 的描述方式,描写函数对象。在采用 JSX 之后,这段代码会如许写:

  1. class Hello extends React.Component {
  2.   render() {
  3.     return <div>Hello {this.props.toWhat}</div>;
  4.   }
  5. }
  6. ReactDOM.render(
  7.   <Hello toWhat="World" />,
  8.   document.getElementById('root')
  9. );
复制代码

通过对比,可以清晰地发现,代码变得更为简便,而且代码结构层次更为清晰。

因为 React 需要将组件转化为虚拟 DOM 树,所以在编写代码时,实际上是在手写一棵结构树。而XML 在树结构的描述上天生具有可读性强的优势。

但如许可读性强的代码仅仅是给写程序的同砚看的,实际上在运行的时候,会使用 Babel 插件将 JSX 语法的代码还原为 React.createElement 的代码。

总结:

JSX 是一个 JavaScript 的语法扩展,结构类似 XML。JSX 主要用于声明 React 元素,但 React 中并不逼迫使用 JSX。即使使用了 JSX,也会在构建过程中,通过 Babel 插件编译为 React.createElement。所以 JSX 更像是 React.createElement 的一种语法糖。

React 团队并不想引入 JavaScript 本身以外的开发体系。而是盼望通过合理的关注点分离保持组件开发的纯粹性。
21. HOC相比 mixins 有什么优点?



HOC 和 Vue 中的 mixins 作用是一致的,而且在早期 React 也是使用 mixins 的方式。但是在使用 class 的方式创建组件以后,mixins 的方式就不能使用了,而且实在 mixins 也是存在一些题目标,好比:



HOC 办理了这些题目,而且它们达成的效果也是一致的,同时也更加的政治正确(毕竟更加函数式了)。
22. React 中的高阶组件运用了什么设计模式?



使用了装饰模式,高阶组件的运用:

  1. function withWindowWidth(BaseComponent) {
  2.   class DerivedClass extends React.Component {
  3.     state = {
  4.       windowWidth: window.innerWidth,
  5.     }
  6.     onResize = () => {
  7.       this.setState({
  8.         windowWidth: window.innerWidth,
  9.       })
  10.     }
  11.     componentDidMount() {
  12.       window.addEventListener('resize', this.onResize)
  13.     }
  14.     componentWillUnmount() {
  15.       window.removeEventListener('resize', this.onResize);
  16.     }
  17.     render() {
  18.       return <BaseComponent {...this.props} {...this.state}/>
  19.     }
  20.   }
  21.   return DerivedClass;
  22. }
  23. const MyComponent = (props) => {
  24.   return <div>Window width is: {props.windowWidth}</div>
  25. };
  26. export default withWindowWidth(MyComponent);
复制代码

装饰模式的特点是不需要改变 被装饰对象 本身,而只是在表面套一个外壳接口。JavaScript 目前已经有了原生装饰器的提案,其用法如下:

  1. @testable
  2.    class MyTestableClass {
  3. }
复制代码


免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。




欢迎光临 ToB企服应用市场:ToB评测及商务社交产业平台 (https://dis.qidao123.com/) Powered by Discuz! X3.4