前端面试大全React

前端面试题大全(React.js)

前端面试题类目分类

  • HTML5 + CSS3
  • JavaScript
  • Vue + Vue3
  • React
  • Webpack
  • 服务端

考点频率 :♥︎ 、 ♥︎ ♥︎、 ♥︎ ♥︎ ♥︎、 ♥︎ ♥︎ ♥︎ ♥︎、 ♥︎ ♥︎ ♥︎ ♥︎ ♥︎

React.js

♥︎♥︎ fetch的延时操作

// fetch语法:fetch(resource, config).then( function(response) { ... } );resource为要获取的资源,
config是配置对象,包含method请求方法,headers请求头信息等
// 定义一个延时函数,返回一个promise
const delayPromise = (timeout=5000) => {
 return new Promise((resolve, reject) => {
 setTimeout(()=>{
 reject(new Error("网络错误"))
 }, timeout)
 })
}
// 定义一个fetch网络请求,返回一个promise
const fetchPromise = (resource, config) => {
 return new Promise((resolve, reject)=>{
 fetch(resource, config).then(res=>{
 resolve(res)
 })
 })
}
// promise的race静态方法接受多个promise对象组成的数组,该数组中哪个promise先执行完成,race方法就返回这个promise的执行结果
const fetchRequest = (resource, config, timeout) => {
 Promise.race([
 delayPromise(timeout), 
 fetchPromise(resource,config)
 ])
}

♥︎♥︎ A 组件嵌套 B 组件,生命周期执行顺序

父组件创建阶段的生命周期钩子函数 constructor
父组件创建阶段的生命周期钩子函数 render
子组件创建阶段的生命周期钩子函数 constructor
子组件创建阶段的生命周期钩子函数 render
子组件创建阶段的生命周期钩子函数 componentDidMount
父组件创建阶段的生命周期钩子函数 componentDidMount

父->父->子->子->子->父

♥︎♥︎ React 组件中 props 和 state 有什么区别?

1、props是从外部传入组件的参数,一般用于父组件向子组件通信,在组件之间通信使用;state一般用于组件内部的状态维护,更新组建内部的数据,状态,更新子组件的props等

2、props不可以在组件内部修改,只能通过父组件进行修改;state在组件内部通过setState修改;

♥︎♥︎ react中组件分为那两种?

类组件和函数组件

♥︎♥︎ 描述 Flux 与 MVC?

传统的 MVC 模式在分离数据(Model)、UI(View和逻辑(Controller)方面工作得很好,但是 MVC 架构经常遇到两个主要问题:

  1. 数据流不够清晰——跨视图发生的级联更新常常会导致混乱的事件网络,难于调试。
  2. 缺乏数据完整性——模型数据可以在任何地方发生突变,从而在整个UI中产生不可预测的结果。

使用 Flux 模式的复杂用户界面不再遭受级联更新,任何给定的React 组件都能够根据 store 提供的数据重建其状态。Flux 模式还通过限制对共享数据的直接访问来加强数据完整性。

♥︎♥︎ 在 React 中使用构造函数和 getInitialState 有什么区别?

// ES6
class MyComponent extends React.Component {
constructor(props) {
super(props);
this.state = { /* initial state */ };
}
}
// ES5
var MyComponent = React.createClass({
getInitialState() {
  return { /* initial state */ };
},
});

本质上其实是等价的?

区别在于ES6和ES5本身,getInitialState 是搭配 React.createClass 使用的, constructor 是搭配React.Component 使用的,在React组件的生命周期中 constructor 先于 getInitialState

♥︎♥︎ 为什么说React是view(视图层)

react, 是 Facebook 推出的一个用来构建用户界面的 JavaScript 库. React 主要用于构建 UI

React被认为是视图层的框架是因为它是基于组件的,一切都是组件,而组件就是渲染页面的基础。不论组件中包含的jsx,methods,state,props,都是属于组件内部的

View(视图)是应用程序中处理数据显示的部分。视图层主要包括二个部分:

1.视图层显示及交互逻辑;

2.视图层的数据结构ViewObj, 包括React中的props和stats;


♥︎♥︎♥︎ React 事件绑定原理

一、react并没有使用原生的浏览器事件,而是在基于Virtual DOM的基础上实现了合成事件,采用小驼峰命名法,默认的事件传播方式是冒泡,如果想改为捕获的话,直接在事件名后面加上Capture即可;事件对象event也不是原生事件对象,而是合成对象,但通过nativeEvent属性可以访问原生事件对象;

二、react合成事件主要分为以下三个过程:

1、事件注册

在该阶段主要做了两件事:document上注册、存储事件回调。所有事件都会注册到document上,

拥有统一的回调函数dispatchEvent来执行事件分发,类似于

document.addEventListener("click",dispatchEvent)。
 register:
 addEventListener-click
 addEventListener-change
listenerBank: 
{ 
click: {key1: fn1, key2: fn2}, 
change: {key1: fn3, key3: fn4} 
} 

2、事件合成

事件触发后,会执行一下过程:

(1)进入统一的事件分发函数dispatchEvent;

(2)找到触发事件的 ReactDOMComponent;

(3)开始事件的合成;

—— 根据当前事件类型生成指定的合成对象

—— 封装原生事件和冒泡机制

—— 查找当前元素以及他所有父级

—— 在listenerBank根据key值查找事件回调并合成到 event(合成事件结束)

3、批处理

批量处理合成事件内的回调函数

♥︎♥︎♥︎ React中的 setState 缺点是什么呢

setState执行的时候可以简单的认为,隶属于原生js执行的空间,那么就是属于同步,被react处理过的空间属于异步,这其实也是一种性能的优化,如果多次使用setState修改值,那么在异步中会先进行合并,再进行渲染,降低了操作dom的次数,具体如下:

(1)setState 在合成事件和钩子函数中是“异步”的,在原生事件和 setTimeout 中都是同步的。

(2)setState的“异步”并不是说内部由异步代码实现,其实本身执行的过程和代码都是同步的,只是合成事件和钩子函数的调用顺序在更新之前,导致在合成事件和钩子函数中没法立马拿到更新后的值,形式了所谓的“异步”,当然可以通过第二个参数 setState(partialState, callback) 中的callback拿到更 新后的结果。

(3)setState 的批量更新优化也是建立在“异步”(合成事件、钩子函数)之上的,在原生事件和setTimeout 中不会批量更新,在“异步”中如果对同一个值进行多次 setState, setState 的批量更新策略会对其进行覆盖,取最后一次的执行,如果是同时 setState 多个不同的值,在更新时会对其进行合并批量更新。

(4)正是由于setState存在异步的机制,如果setState修改值的时候依赖于state本身的值,有时候并不可靠,这时候我们需要传入一个回调函数作为其入参,这个回调函数的第一个参数为更新前的state值。

♥︎♥︎♥︎ React组件通信如何实现

react本身:

(1) props ——父组件向子组件通过props传参

(2) 实例方法——在父组件中可以用 refs 引用子组件,之后就可以调用子组件的实例方法了

(3) 回调函数——用于子组件向父组件通信,子组件调用props传递过来的方法

(4) 状态提升——两个子组件可以通过父组件定义的参数进行传参

(5) Context上下文——一般用作全局主题

(6) Render Props——渲染的细节由父组件控制

状态管理:

(1) mobx/redux/dva——通过在view中触发action,改变state,进而改变其他组件的view

♥︎♥︎♥︎ 类组件和函数组件的区别

(1)语法上:函数组件是一个函数,返回一个jsx元素,而类组件是用es6语法糖class定义,继承component这个类

(2)类组件中可以通过state进行状态管理,而在函数组件中不能使用setState(),在react16.8以后,函数组件可以通过hooks中的useState来模拟类组件中的状态管理;

(3)类组件中有一系列的生命周期钩子函数,在函数组件中也需要借助hooks来使用生命周期函数;

(4)类组件能够捕获最新的值(永远保持一致),这是因为当实例的props属性发生修改时,class组件能够直接通过this捕获到组件最新的props;而函数式组件是捕获渲染所使用的值,已经因为javascript闭包的特性,之前的props参数保存在内存之中,无法从外部进行修改。

♥︎♥︎♥︎ 请你说说React的路由是什么?

路由分为前端路由和后端路由,后端路由是服务器根据用户发起的请求而返回不同内容,前端路由是客户端根据不同的URL去切换组件;在web应用前端开发中,路由系统是最核心的部分,当页面的URL发生改变时,页面的显示结果可以根据URL的变化而变化,但是页面不会刷新。

react生态中路由通常是使用react-router来进行配置,其主要构成为:

(1)Router——对应路由的两种模式,包括与BrowserRouter与HashRoute;

(2)route matching组件——控制路径对应的显示组件,可以进行同步加载和异步加载;

(3)navigation组件——用做路由切换和跳转;

BrowserRouter与HashRouter的区别:

(1)底层原理不一样:BrowserRouter使用的是H5的history API,不兼容IE9及以下版本;HashRouter使用的是URL的哈希值;

(2)path表现形式不一样:BrowserRouter的路径中没有#,例如:localhost:3000/demo/test;HashRouter的路径包含#,例如:localhost:3000/#/demo/test;

(3)刷新后对路由state参数的影响:BrowserRouter没有任何影响,因为state保存在history对象中;

react生态中路由通常是使用react-router来进行配置,其主要构成为:

BrowserRouter与HashRouter的区别:HashRouter刷新后会导致路由state参数的丢失;

♥︎♥︎♥︎ 聊聊 Redux 和 Vuex 的设计思想

Flux的核心思想就是数据和逻辑永远单向流动,由三大部分组成 dispatcher(负责分发事件),store(负责保存数据,同时响应事件并更新数据)和 view(负责订阅store中的数据,并使用这些数据渲染相应的页面),Redux和Vuex是flux思想的具体实现,都是用来做状态管理的工具,Redux主要在react中使用,Vuex主要在vue中使用。

Redux设计和使用的三大原则: 
(1)单一的数据源:整个应用的 state被储存在唯一一个 store中; 
(2)状态是只读的:Store.state不能直接修改(只读),必须调用dispatch(action) => store.reducer => return newState;action是一个对象,有type(操作类型)和payload(新值) 属性;
(3)状态修改均由纯函数完成:在Redux中,通过纯函数reducer来确定状态的改变,因为reducer是纯 函数,所以相同的输入,一定会得到相同的输出,同时也不支持异步;返回值是一个全新的state; 

vuex由State + Muatations(commit) + Actions(dispatch) 组成: 
(1)全局只有一个Store实例(单一数据源);
(2)Mutations必须是同步事务,不同步修改的话,会很难调试,不知道改变什么时候发生,也很难确定 先后顺序,A、B两个mutation,调用顺序可能是A -> B,但是最终改变 State的结果可能是B -> A; 
(3)Actions负责处理异步事务,然后在异步回调中触发一个或多个mutations,也可以在业务代码中处 理异步事务,然后在回调中同样操作; 
(4)模块化通过module方式来处理,这个跟Redux-combineReducer类似,在应用中可以通过 namespaceHelper来简化使用;

♥︎♥︎♥︎ React中不同组件之间如何做到数据交互?

正向传值–使用 props

父组件发送数据在子组件中使用 this.props.xxx 来接收数据,如果父级的某个props 改变了,React 会重渲染所有的子节点

逆向传值—函数传值

  • 子组件通过事件调用函数传递
  • 在子组件中使用 this.props调用的函数名绑定发送数据。
  • 在父组件中进行函数传递。
  • 父组件中必须要有一个形参用来接收子组件发送过来的数据。

同级传值—pubsub-js

  • 在第一个要传值的组件中进行数据抛出 PubSub.publish(“ 事件名”,”数据”)。
  • 在第二个要接收数据的组件中接收 PubSub.subscribe(“监听的事件”,(事件,数 据)=>{})。

跨组件传值—context

context 上下文对象,无需为每一层组件手动添加 props,就能在组件数间进行数据传递的方法。
使用 createContext()方法提供了两个对象 - Provider 对象生产者—->用来生产数据 - Consumer对象消费者—->用来使用数据。

♥︎♥︎♥︎ React中refs的作用是什么?

ref是React提供的用来操纵React组件实例或者DOM元素的接口。主要用来做文本框的聚焦、触发强制动画等;

//类组件
class Foo extends React.Component {
   constructor(props) {
 super(props)
 this.myRef = React.createRef()
 }
 render() {
 return }
  
<input ref={ this.myRef } /> <button onClick = {()=>this.handle()}>聚焦
} 
handle() { 
 // 通过current属性访问到当前元素 this.myRef.current.focus() 
} 
// 函数组件 function
Foo() { const inputEl = useRef(null) const handle = () => { inputEl.current.focus() } 
return
<input type="text" ref={ inputEl }/> 聚焦}

♥︎♥︎♥︎ 组件绑定和js原生绑定事件哪个先执行?

先执行js原生绑定事件,再执行合成事件,因为合成事件是发生在冒泡阶段

♥︎♥︎♥︎ diff 和 Key 之间的联系

diff算法即差异查找算法,对于DOM结构即为tree的差异查找算法,只有在React更新阶段才会有Diff算法的运用;react的diff运算为了降低时间复杂度,是按层比较新旧两个虚拟dom树的。diff运算的主要流程见下:

1、tree diff : 新旧两棵dom树,逐层对比的过程就是 tree diff, 当整棵DOM树逐层对比完毕,则所有需要被按需更新的元素,必然能够被找到。 
2、component diff : 在进行tree diff的时候,每一层中,都有自己的组件,组件级别的对比, 叫做 component diff。如果对比前后,组件的类型相同,则暂时认为此组件不需要更新;如果对比前后, 组件的类型不同,则需要移除旧组件,创建新组件,并渲染到页面上。 React只会匹配类型相同的组件,也就是说如果<A>被<B>替换,那么React将直接删除A组件然后创建一 个B组件;如果某组件A转移到同层B组件上,那么这个A组件会先被销毁,然后在B组件下重新生成,以A为根节 点的树整个都被重新创建,这会比较耗费性能,但实际上我们很少跨层移动dom节点,一般都是同层横向移动; 
3、element diff :在进行组件对比的时候,如果两个组件类型相同,则需要进行元素级别的对比, 这叫做element diff。 对于列表渲染,react会在创建时要求为每一项输入一个独一无二的key,这样就能进行高效的diff运算 了。比如我们要在b和c节点中间插入一个节点f,jquery会将f这个节点后面的每一个节点都进行更新,比如c 更新成f,d更新成c,e更新成d,这样操作的话就会特别多,而加了key的react咋不会频繁操作dom,而是优先采用移动的方式,找到正确的位置去插入新节点;所以我们不能省略key值,因为在对比两个新旧的子元素是通过key值来精确地判断两个节点是否为同一个,如果没有key的话则是见到谁就更新谁,非常耗费性能。 

当我们通过this.setState()改变数据的时候,React会将其标记为脏节点,在事件循环的最后 才会重新渲染所有的脏节点以及脏节点的子树;另外我们可以使用shouldComponentUpdate这个生命周期来 选择性的渲染子树,可以基于组件之前的状态或者下一个状态来决定它是否需要重新渲染,这样的话可以组织 重新渲染大的子树。

♥︎♥︎♥︎ 虚拟 dom 和原生 dom

(1)原生dom是浏览器通过dom树渲染的复杂对象,属性非常多;

(2)虚拟dom是存在于内存中的js对象,属性远少于原生的dom对象,它用来描述真实的dom,并不会直接在浏览器中显示;

(3)原生dom操作、频繁排版与重绘的效率是相当低的,虚拟dom则是利用了计算机内存高效的运算性能减少了性能的损耗;

(4)虚拟DOM进行频繁修改,然后一次性比较并修改真实DOM中需要改的部分,最后并在真实DOM中对修改部分进行排版与重绘,减少过多DOM节点排版与重绘损耗

♥︎♥︎♥︎ react中如何打包上传图片文件

通过 base64 前端处理图片为 base64 的解决方案

  • 利用 FileReader 对数据进行读取,如果是图片会将图片读取为 base64 的形式
  • 将得到的 base64 的字符串传给后端
  • 后端直接保存该html字符串,之后调用接口查询该数据直接前端通过img标签完成自动解析即可

代码实现:

function App() {
  const handleFileChange = e => {
    const file = e.currentTarget.files[0];
    const reader = new FileReader();

    reader.onload = function() {
      // reader.results当完成onload后会将图片转为base64
      // 后端只要解析base64对应的字符串即可
      const result = this.result;
      console.log(result);
    };

    reader.readAsDataURL(file); // 得到经过base64编码的图片信息
  };

  return (
    <div className="App">
      <input type="file" onChange={handleFileChange} />
    </div>
  );
}

利用input,xhr,formData来实现

  • 利用input[type=‘file’]来实现
  • 点击选择文件,且选择文件完毕后,触发onChange事件
  • 通过event.targer.files(react中)获取所选文件
  • 通过FormData这个类,将文件添加到其实例中
  • 配置xhr,通过POST的方式发送formData到后端即可

代码实现:

import React, { useState } from 'react';
import { ApiHost } from '../../constant';
import { Button, FormControl, Progress } from 'zent';

export type UploadCompletCallback<T> = (
  e: ProgressEvent<XMLHttpRequestEventTarget>,
  reponse: T
) => void;

export type UploadStartCallback = (
  e: ProgressEvent<XMLHttpRequestEventTarget>
) => void;

export type UploadProcessCallback = (loaded: number, total: number) => void;

interface Props {
  onComplete?: UploadCompletCallback<any>;
  onStart?: UploadStartCallback;
  onProcess?: UploadProcessCallback;
  hasProcess?: boolean;
  title: string;
}

export const UploadBtn: React.FC<Props> = props => {
  const { onComplete, onStart, onProcess, hasProcess = false, title } = props;
  const [progress, setProgress] = useState<number>(0);
  const [uploadStatus, setStatus] = useState<boolean>(false);

  // 处理文件上传的核心方法
  const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
    const files = e.target.files;

    if (files) {
      if (!files.length) return;

  // 将文件处理成formData
      let formData = new FormData();
      for (let k in files) {
        formData.append('file', files[k], window.encodeURI(files[k].name));
      }

      let xhr = new XMLHttpRequest();

      xhr.responseType = 'json';
      xhr.timeout = 5000;
      xhr.open('POST', `${ApiHost}/user/upload`, true);

   // 事件监听
      xhr.addEventListener('loadstart', e => {
        setProgress(0);
        setStatus(true);
        onStart && onStart(e);
      });

      xhr.upload.onprogress = function(e) {
        const { total, loaded } = e;
        setProgress((loaded / total) * 100);
        onProcess && onProcess(loaded, total);
      };

      xhr.addEventListener('load', e => {
        const result = xhr.response;
        onComplete && onComplete(e, result);
      });

      xhr.send(formData);
    } else {
      return;
    }
  };

  const selectFile = () => {
    const btn = document.querySelector("input[type='file']");
    
 //@ts-ignore
    btn && btn.click();
  };
  return (
    <FormControl label={title}>
      <input type="file" onChange={handleChange} style={{ display: 'none' }} />
      <Button onClick={selectFile}>上传</Button>
      {hasProcess && uploadStatus && <Progress percent={progress}></Progress>}
    </FormControl>
  );
};

后端对于该文件上传请求的处理nodejs版本

主要步骤:

  • 利用fs.createReadStream 来读取本地的传过去文件的地址
  • 利用fs.createWriteStream来写到服务器上的某个文件地址
  • 通过reader.pipe来将readFile里面的内容复制到writeFile中

♥︎♥︎♥︎ 对单向数据流和双向数据绑定的理解,好处?

react的单向数据流是指只允许父组件向子组件传递数据,子组件绝对不能修改父组件传的数据,如果想要修改数据,则要在子组件中执行父组件传递过来的回调函数,提醒父组件对数据进行修改。数据单向流让所有的状态改变可以追溯,有利于应用的可维护性;

angular中实现了双向数据绑定,代码编写方便,但是不利于维护

♥︎♥︎♥︎ React 按需加载

1、使用React.lazy, 但是React.lazy技术还不支持服务端渲染

const OtherComponent = React.lazy(() => import('./OtherComponent'))

2、使用Loadable Components这个库

import loadable from '@loadable/component'
const OtherComponent = loadable(() => import('./OtherComponent'))

♥︎♥︎♥︎ React 实现目录树(组件自身调用自身)

需求描述:实现n级属性目录展开,该实现使用react,antd-mobile

使用方法

import TreesMenu from "./treeMenu.jsx";
<TreesMenu
       datas={treesData}
       onselected={item => {
           console.log("选择了树形目录的数据:", item);
       }}
></TreesMenu>

实现数据

export let treesData = [{"Childs":[{"Childs":[],"DeptName":"班子成员","ShortName":"班子成员","DeptCode":"81","ParentID":1,"Layer":1,"Path":",0,1,","Sequence":0,"IsEnabled":1,"IsUnit":0,"ID":2073408498041056},{"Childs":[],"DeptName":"调研员","ShortName":"调研员","DeptCode":"82","ParentID":1,"Layer":1,"Path":",0,1,","Sequence":0,"IsEnabled":1,"IsUnit":0,"ID":2073408531598048},{"Childs":[],"DeptName":"党政办公室","ShortName":"党政办公室","DeptCode":"83","ParentID":1,"Layer":1,"Path":",0,1,","Sequence":0,"IsEnabled":1,"IsUnit":0,"ID":2073408548375776},{"Childs":[],"DeptName":"党政办公室(财务)","ShortName":"党政办公室(财务)","DeptCode":"84","ParentID":1,"Layer":1,"Path":",0,1,","Sequence":0,"IsEnabled":1,"IsUnit":0,"ID":2073408581929184},{"Childs":[],"DeptName":"党建办公室","ShortName":"党建办公室","DeptCode":"85","ParentID":1,"Layer":1,"Path":",0,1,","Sequence":0,"IsEnabled":1,"IsUnit":0,"ID":2073408598709728},{"Childs":[],"DeptName":"平安办公室","ShortName":"平安办公室","DeptCode":"86","ParentID":1,"Layer":1,"Path":",0,1,","Sequence":0,"IsEnabled":1,"IsUnit":0,"ID":2073408615482336},{"Childs":[],"DeptName":"信访办公室","ShortName":"信访办公室","DeptCode":"87","ParentID":1,"Layer":1,"Path":",0,1,","Sequence":0,"IsEnabled":1,"IsUnit":0,"ID":2073408632263392},{"Childs":[],"DeptName":"管理办公室","ShortName":"管理办公室","DeptCode":"88","ParentID":1,"Layer":1,"Path":",0,1,","Sequence":0,"IsEnabled":1,"IsUnit":0,"ID":2073408649039584},{"Childs":[],"DeptName":"自治办公室","ShortName":"自治办公室","DeptCode":"89","ParentID":1,"Layer":1,"Path":",0,1,","Sequence":0,"IsEnabled":1,"IsUnit":0,"ID":2073408665815776},{"Childs":[],"DeptName":"综合事务办公室","ShortName":"综合事务办公室","DeptCode":"90","ParentID":1,"Layer":1,"Path":",0,1,","Sequence":0,"IsEnabled":1,"IsUnit":0,"ID":2073408665819872},{"Childs":[],"DeptName":"监察办公室","ShortName":"监察办公室","DeptCode":"91","ParentID":1,"Layer":1,"Path":",0,1,","Sequence":0,"IsEnabled":1,"IsUnit":0,"ID":2073408682596576},{"Childs":[],"DeptName":"武装部","ShortName":"武装部","DeptCode":"92","ParentID":1,"Layer":1,"Path":",0,1,","Sequence":0,"IsEnabled":1,"IsUnit":0,"ID":2073408699368672},{"Childs":[],"DeptName":"司法所","ShortName":"司法所","DeptCode":"93","ParentID":1,"Layer":1,"Path":",0,1,","Sequence":0,"IsEnabled":1,"IsUnit":0,"ID":2073408699371232},{"Childs":[],"DeptName":"城运中心","ShortName":"城运中心","DeptCode":"94","ParentID":1,"Layer":1,"Path":",0,1,","Sequence":0,"IsEnabled":1,"IsUnit":0,"ID":2073408716144864},{"Childs":[],"DeptName":"工会","ShortName":"工会","DeptCode":"95","ParentID":1,"Layer":1,"Path":",0,1,","Sequence":0,"IsEnabled":1,"IsUnit":0,"ID":2073408749699296},{"Childs":[],"DeptName":"团委","ShortName":"团委","DeptCode":"96","ParentID":1,"Layer":1,"Path":",0,1,","Sequence":0,"IsEnabled":1,"IsUnit":0,"ID":2073408749700320},{"Childs":[],"DeptName":"妇联","ShortName":"妇联","DeptCode":"97","ParentID":1,"Layer":1,"Path":",0,1,","Sequence":0,"IsEnabled":1,"IsUnit":0,"ID":2073408749703648},{"Childs":[],"DeptName":"房管办","ShortName":"房管办","DeptCode":"98","ParentID":1,"Layer":1,"Path":",0,1,","Sequence":0,"IsEnabled":1,"IsUnit":0,"ID":2073408749704672},{"Childs":[],"DeptName":"安监所","ShortName":"安监所","DeptCode":"99","ParentID":1,"Layer":1,"Path":",0,1,","Sequence":0,"IsEnabled":1,"IsUnit":0,"ID":2073408766480352},{"Childs":[],"DeptName":"党建服务中心","ShortName":"党建服务中心","DeptCode":"100","ParentID":1,"Layer":1,"Path":",0,1,","Sequence":0,"IsEnabled":1,"IsUnit":0,"ID":2073408783258080},{"Childs":[],"DeptName":"社区事务受理中心","ShortName":"社区事务受理中心","DeptCode":"101","ParentID":1,"Layer":1,"Path":",0,1,","Sequence":0,"IsEnabled":1,"IsUnit":0,"ID":2073408816808672},{"Childs":[],"DeptName":"社区文化中心","ShortName":"社区文化中心","DeptCode":"102","ParentID":1,"Layer":1,"Path":",0,1,","Sequence":0,"IsEnabled":1,"IsUnit":0,"ID":2073408917476576},{"Childs":[],"DeptName":"社区学校","ShortName":"社区学校","DeptCode":"103","ParentID":1,"Layer":1,"Path":",0,1,","Sequence":0,"IsEnabled":1,"IsUnit":0,"ID":2073408951031264},{"Childs":[],"DeptName":"绿化市容","ShortName":"绿化市容","DeptCode":"104","ParentID":1,"Layer":1,"Path":",0,1,","Sequence":0,"IsEnabled":1,"IsUnit":0,"ID":2073408951033824},{"Childs":[],"DeptName":"周家渡城管中队","ShortName":"周家渡城管中队","DeptCode":"105","ParentID":1,"Layer":1,"Path":",0,1,","Sequence":0,"IsEnabled":1,"IsUnit":0,"ID":2073408967806688},{"Childs":[],"DeptName":"商会","ShortName":"商会","DeptCode":"106","ParentID":1,"Layer":1,"Path":",0,1,","Sequence":0,"IsEnabled":1,"IsUnit":0,"ID":2073408967810016},{"Childs":[],"DeptName":"开发人员","ShortName":"开发人员","DeptCode":"107","ParentID":1,"Layer":1,"Path":",0,1,","Sequence":0,"IsEnabled":1,"IsUnit":0,"ID":2073408967811040},{"Childs":[{"Childs":[],"DeptName":"上南八村","ShortName":"上南八村","DeptCode":"109","ParentID":2073409034918112,"Layer":2,"Path":",0,1,2073409034918112,","Sequence":0,"IsEnabled":1,"IsUnit":0,"ID":2073409034918368},{"Childs":[],"DeptName":"上南四村","ShortName":"上南四村","DeptCode":"110","ParentID":2073409034918112,"Layer":2,"Path":",0,1,2073409034918112,","Sequence":0,"IsEnabled":1,"IsUnit":0,"ID":2073409051695840},{"Childs":[],"DeptName":"上南五村","ShortName":"上南五村","DeptCode":"111","ParentID":2073409034918112,"Layer":2,"Path":",0,1,2073409034918112,","Sequence":0,"IsEnabled":1,"IsUnit":0,"ID":2073409068471776},{"Childs":[],"DeptName":"上南六村","ShortName":"上南六村","DeptCode":"112","ParentID":2073409034918112,"Layer":2,"Path":",0,1,2073409034918112,","Sequence":0,"IsEnabled":1,"IsUnit":0,"ID":2073409102022368},{"Childs":[],"DeptName":"上南七村","ShortName":"上南七村","DeptCode":"113","ParentID":2073409034918112,"Layer":2,"Path":",0,1,2073409034918112,","Sequence":0,"IsEnabled":1,"IsUnit":0,"ID":2073409118800352},{"Childs":[],"DeptName":"上南九村","ShortName":"上南九村","DeptCode":"114","ParentID":2073409034918112,"Layer":2,"Path":",0,1,2073409034918112,","Sequence":0,"IsEnabled":1,"IsUnit":0,"ID":2073409135576544},{"Childs":[],"DeptName":"齐河一","ShortName":"齐河一","DeptCode":"115","ParentID":2073409034918112,"Layer":2,"Path":",0,1,2073409034918112,","Sequence":0,"IsEnabled":1,"IsUnit":0,"ID":2073409152353760},{"Childs":[],"DeptName":"齐河二","ShortName":"齐河二","DeptCode":"116","ParentID":2073409034918112,"Layer":2,"Path":",0,1,2073409034918112,","Sequence":0,"IsEnabled":1,"IsUnit":0,"ID":2073409152360928},{"Childs":[],"DeptName":"上南十村1","ShortName":"上南十村1","DeptCode":"117","ParentID":2073409034918112,"Layer":2,"Path":",0,1,2073409034918112,","Sequence":0,"IsEnabled":1,"IsUnit":0,"ID":2073409169135840},{"Childs":[],"DeptName":"上南十二村","ShortName":"上南十二村","DeptCode":"118","ParentID":2073409034918112,"Layer":2,"Path":",0,1,2073409034918112,","Sequence":0,"IsEnabled":1,"IsUnit":0,"ID":2073409185911520},{"Childs":[],"DeptName":"上南十村2","ShortName":"上南十村2","DeptCode":"119","ParentID":2073409034918112,"Layer":2,"Path":",0,1,2073409034918112,","Sequence":0,"IsEnabled":1,"IsUnit":0,"ID":2073409202686432},{"Childs":[],"DeptName":"上南十一村","ShortName":"上南十一村","DeptCode":"120","ParentID":2073409034918112,"Layer":2,"Path":",0,1,2073409034918112,","Sequence":0,"IsEnabled":1,"IsUnit":0,"ID":2073409219461344},{"Childs":[],"DeptName":"都市庭院","ShortName":"都市庭院","DeptCode":"121","ParentID":2073409034918112,"Layer":2,"Path":",0,1,2073409034918112,","Sequence":0,"IsEnabled":1,"IsUnit":0,"ID":2073409219466976},{"Childs":[],"DeptName":"川新","ShortName":"川新","DeptCode":"122","ParentID":2073409034918112,"Layer":2,"Path":",0,1,2073409034918112,","Sequence":0,"IsEnabled":1,"IsUnit":0,"ID":2073409236243680},{"Childs":[],"DeptName":"雪野二村","ShortName":"雪野二村","DeptCode":"123","ParentID":2073409034918112,"Layer":2,"Path":",0,1,2073409034918112,","Sequence":0,"IsEnabled":1,"IsUnit":0,"ID":2073409253021152},{"Childs":[],"DeptName":"上南花苑","ShortName":"上南花苑","DeptCode":"124","ParentID":2073409034918112,"Layer":2,"Path":",0,1,2073409034918112,","Sequence":0,"IsEnabled":1,"IsUnit":0,"ID":2073409269798368},{"Childs":[],"DeptName":"昌里花园","ShortName":"昌里花园","DeptCode":"125","ParentID":2073409034918112,"Layer":2,"Path":",0,1,2073409034918112,","Sequence":0,"IsEnabled":1,"IsUnit":0,"ID":2073409286570976},{"Childs":[],"DeptName":"恒大","ShortName":"恒大","DeptCode":"126","ParentID":2073409034918112,"Layer":2,"Path":",0,1,2073409034918112,","Sequence":0,"IsEnabled":1,"IsUnit":0,"ID":2073409303348192},{"Childs":[],"DeptName":"上南三村","ShortName":"上南三村","DeptCode":"127","ParentID":2073409034918112,"Layer":2,"Path":",0,1,2073409034918112,","Sequence":0,"IsEnabled":1,"IsUnit":0,"ID":2073409303353824},{"Childs":[],"DeptName":"上南一村","ShortName":"上南一村","DeptCode":"128","ParentID":2073409034918112,"Layer":2,"Path":",0,1,2073409034918112,","Sequence":0,"IsEnabled":1,"IsUnit":0,"ID":2073409320130784},{"Childs":[],"DeptName":"上南二村","ShortName":"上南二村","DeptCode":"129","ParentID":2073409034918112,"Layer":2,"Path":",0,1,2073409034918112,","Sequence":0,"IsEnabled":1,"IsUnit":0,"ID":2073409336908000},{"Childs":[],"DeptName":"昌里五","ShortName":"昌里五","DeptCode":"130","ParentID":2073409034918112,"Layer":2,"Path":",0,1,2073409034918112,","Sequence":0,"IsEnabled":1,"IsUnit":0,"ID":2073409353685728},{"Childs":[],"DeptName":"云台一","ShortName":"云台一","DeptCode":"131","ParentID":2073409034918112,"Layer":2,"Path":",0,1,2073409034918112,","Sequence":0,"IsEnabled":1,"IsUnit":0,"ID":2073409370462176},{"Childs":[],"DeptName":"云台二","ShortName":"云台二","DeptCode":"132","ParentID":2073409034918112,"Layer":2,"Path":",0,1,2073409034918112,","Sequence":0,"IsEnabled":1,"IsUnit":0,"ID":2073409387237344},{"Childs":[],"DeptName":"云莲一","ShortName":"云莲一","DeptCode":"133","ParentID":2073409034918112,"Layer":2,"Path":",0,1,2073409034918112,","Sequence":0,"IsEnabled":1,"IsUnit":0,"ID":2073409404012512},{"Childs":[],"DeptName":"昌里七","ShortName":"昌里七","DeptCode":"134","ParentID":2073409034918112,"Layer":2,"Path":",0,1,2073409034918112,","Sequence":0,"IsEnabled":1,"IsUnit":0,"ID":2073409420789472},{"Childs":[],"DeptName":"动迁联合","ShortName":"动迁联合","DeptCode":"135","ParentID":2073409034918112,"Layer":2,"Path":",0,1,2073409034918112,","Sequence":0,"IsEnabled":1,"IsUnit":0,"ID":2073409437567200},{"Childs":[],"DeptName":"齐河四","ShortName":"齐河四","DeptCode":"146","ParentID":2073409034918112,"Layer":2,"Path":",0,1,2073409034918112,","Sequence":0,"IsEnabled":1,"IsUnit":0,"ID":2073409487902688},{"Childs":[],"DeptName":"齐河三","ShortName":"齐河三","DeptCode":"147","ParentID":2073409034918112,"Layer":2,"Path":",0,1,2073409034918112,","Sequence":0,"IsEnabled":1,"IsUnit":0,"ID":2073409504677856},{"Childs":[],"DeptName":"昌里四","ShortName":"昌里四","DeptCode":"148","ParentID":2073409034918112,"Layer":2,"Path":",0,1,2073409034918112,","Sequence":0,"IsEnabled":1,"IsUnit":0,"ID":2073409521452768},{"Childs":[],"DeptName":"齐河八","ShortName":"齐河八","DeptCode":"149","ParentID":2073409034918112,"Layer":2,"Path":",0,1,2073409034918112,","Sequence":0,"IsEnabled":1,"IsUnit":0,"ID":2073409538229984},{"Childs":[],"DeptName":"齐河五","ShortName":"齐河五","DeptCode":"150","ParentID":2073409034918112,"Layer":2,"Path":",0,1,2073409034918112,","Sequence":0,"IsEnabled":1,"IsUnit":0,"ID":2073409555007968},{"Childs":[],"DeptName":"齐河七","ShortName":"齐河七","DeptCode":"151","ParentID":2073409034918112,"Layer":2,"Path":",0,1,2073409034918112,","Sequence":0,"IsEnabled":1,"IsUnit":0,"ID":2073409555013600}],"DeptName":"居委会","ShortName":"居委会","DeptCode":"108","ParentID":1,"Layer":1,"Path":",0,1,","Sequence":0,"IsEnabled":1,"IsUnit":0,"ID":2073409034918112},{"Childs":[{"Childs":[],"DeptName":"办公室","ShortName":"办公室","DeptCode":"137","ParentID":2073409437571296,"Layer":2,"Path":",0,1,2073409437571296,","Sequence":0,"IsEnabled":1,"IsUnit":0,"ID":2073409437571552},{"Childs":[],"DeptName":"事务所","ShortName":"事务所","DeptCode":"138","ParentID":2073409437571296,"Layer":2,"Path":",0,1,2073409437571296,","Sequence":0,"IsEnabled":1,"IsUnit":0,"ID":2073409454343904}],"DeptName":"队伍建设办公室","ShortName":"队伍建设办公室","DeptCode":"136","ParentID":1,"Layer":1,"Path":",0,1,","Sequence":0,"IsEnabled":1,"IsUnit":0,"ID":2073409437571296},{"Childs":[{"Childs":[],"DeptName":"办公室","ShortName":"办公室","DeptCode":"140","ParentID":2073409454347232,"Layer":2,"Path":",0,1,2073409454347232,","Sequence":0,"IsEnabled":1,"IsUnit":0,"ID":2073409454347488},{"Childs":[],"DeptName":"慈善超市","ShortName":"慈善超市","DeptCode":"141","ParentID":2073409454347232,"Layer":2,"Path":",0,1,2073409454347232,","Sequence":0,"IsEnabled":1,"IsUnit":0,"ID":2073409471121632},{"Childs":[],"DeptName":"残联","ShortName":"残联","DeptCode":"142","ParentID":2073409454347232,"Layer":2,"Path":",0,1,2073409454347232,","Sequence":0,"IsEnabled":1,"IsUnit":0,"ID":2073409471122656},{"Childs":[],"DeptName":"计生办","ShortName":"计生办","DeptCode":"143","ParentID":2073409454347232,"Layer":2,"Path":",0,1,2073409454347232,","Sequence":0,"IsEnabled":1,"IsUnit":0,"ID":2073409471124448},{"Childs":[],"DeptName":"爱卫办","ShortName":"爱卫办","DeptCode":"144","ParentID":2073409454347232,"Layer":2,"Path":",0,1,2073409454347232,","Sequence":0,"IsEnabled":1,"IsUnit":0,"ID":2073409487897568},{"Childs":[],"DeptName":"老龄办","ShortName":"老龄办","DeptCode":"145","ParentID":2073409454347232,"Layer":2,"Path":",0,1,2073409454347232,","Sequence":0,"IsEnabled":1,"IsUnit":0,"ID":2073409487900896},{"Childs":[],"DeptName":"居家养老服务中心","ShortName":"居家养老服务中心","DeptCode":"152","ParentID":2073409454347232,"Layer":2,"Path":",0,1,2073409454347232,","Sequence":0,"IsEnabled":1,"IsUnit":0,"ID":2080463921349388},{"Childs":[],"DeptName":"综合为老服务中心","ShortName":"综合为老服务中心","DeptCode":"153","ParentID":2073409454347232,"Layer":2,"Path":",0,1,2073409454347232,","Sequence":0,"IsEnabled":1,"IsUnit":0,"ID":2080464005240076}],"DeptName":"服务办公室","ShortName":"服务办公室","DeptCode":"139","ParentID":1,"Layer":1,"Path":",0,1,","Sequence":0,"IsEnabled":1,"IsUnit":0,"ID":2073409454347232}],"DeptName":"周家渡街道办事处","ShortName":"周家渡街道办事处","DeptCode":"SHRGBTRJJSYXGS ","ParentID":0,"Layer":0,"Path":",0,","Sequence":11,"LeaderID":0,"ManagerID":0,"IsEnabled":1,"IsUnit":1,"ID":1,"SystemID":1}];

实现代码

import React from "react";
import { Accordion, List } from "antd-mobile";

class TreesMenu extends React.Component {
  render() {
    let datas = this.props.datas;
    return (
      <div>
        {datas instanceof Array ? (
          <div>
            {datas.map((d, index) => {
              return <TreeNode datas={d} key={index} onselected={(item) => this.props.onselected(item)}></TreeNode>;
            })}
          </div>
        ) : (
          <div>请传入正确格式</div>
        )}
      </div>
    );
  }
}

export default TreesMenu;

class TreeNode extends React.Component {
  renderItems(datas) {
    return (
      <List className="my-list">
        <List.Item>content 1</List.Item>
        <List.Item>content 2</List.Item>
        <List.Item>content 3</List.Item>
      </List>
    );
  }
  render() {
    let data = this.props.datas;
    return data && (!data.Childs || data.Childs.length === 0) ? (
      <div>
        <List className="my-list" onClick={() => {
            this.props.onselected(data)
        }}>
          <List.Item>{data.DeptName}</List.Item>
        </List>
      </div>
    ) : (
      <div>
        <Accordion
          onChange={this.onChange}
        >
          <Accordion.Panel header={data.DeptName} className="tree-accordion-panel">
            {data && data.Childs && data.Childs.map((item, index) => {
              return <TreeNode datas={item} onselected={(item) => this.props.onselected(item)} key={index}></TreeNode>;
            })}
          </Accordion.Panel>
        </Accordion>
      </div>
    );
  }
}

♥︎♥︎♥︎ 如果我进行三次setState会发生什么

如果是在原生js空间,则会同步执行,修改三次state的值,调用三次render函数;

如果是在react函数空间下,则会进行合并,只修改一次state的值,调用一次render。

♥︎♥︎♥︎ 渲染一个react组件的过程

1、babel编译
 当我们对代码进行编译的时候,babel会将我们在组件中编写的jsx代码转化为React.createElement的表达式,createElement方法有三个参数,分别为type(元素类型)、attributes(元素所有属性)、children(元素所有子节点);2、生成element
 当render方法被触发以后,createElement方法会执行,返回一个element对象,这个对象描述了真实节点的信息,其实就是虚拟dom节点;
3、生成真实节点(初次渲染)
 这时候我们会判断element的类型,如果是null、false则实例一个ReactDOMEmptyComponent对 象; 是string、number类型的话则实例一个ReactDOMTextComponent对象; 如果element是对象的话,会进一步判断type元素类型,是原生dom元素,则实例化ReactDOMComponent; 如果是自定义组件,则实例化ReactCompositeComponentWrapper;在这些类生成实例对象的时候,在其内部会调用 mountComponent方法,这个方法里面有一系列浏览器原生dom方法,可以将element渲染成真实的dom并插入到文档中;
 4、生命周期
 componentDidMount:会在组件挂载后(插入DOM树中) 立即调用。一般可以在这里请求数据;
 componentDidUpdate:会在数据更新后立即调用,首次渲染不会执行此方法;可以在其中直接调用 setState,但必须用if语句进行判断,防止死循环;
 conponentWillUnmount:会在组件卸载及销毁之前调用,在此方法中执行必要的清理操作,如清除timer;
 static getDerivedStateFromProps(prps,state):这个生命周期函数代替了componentWillMount和componentWillUpdate生命周期;props和state发生改变则调用,在初始化挂载及后续更新时都会被调用,返回一个对象来更新state,如果返回null则不更新任何内容;
 shouldComponentUpdate(nextProps,nextState):这个生命周期函数的返回值用来判断React组件是否因为当前 state 或 props 更改而重新渲染,默认返回值是true;这个方法在初始化渲染或使用forceUpdate()时不会调用;当将旧的state的值原封不动赋值给新的state(即不改变state的值,但是调用了setState)和 无数据交换的父组件的重新渲染都会导致组件重新渲染,这时候都可以通过shouldComponentUpdate来优化。

♥︎♥︎♥︎ useEffect 和 useLayoutEffect 的区别

useEffect和useLayout都是副作用hooks,两则非常相似,同样都接收两个参数:

(1)第一个参数为函数,第二个参数为依赖列表,只有依赖更新时才会执行函数;返回一个函数,当页面刷新的或销毁的时候执行return后的代码;

(2)如果不接受第二个参数,那么在第一次渲染完成之后和每次更新渲染页面的时候,都会调用useEffect的回调函数;

useEffect和 useLayout的主要区别就是他们的执行时机不同,在浏览器中js线程与渲染线程是互斥的,当js线程执行时,渲染线程呈挂起状态,只有当js线程空闲时渲染线程才会执行,将生成的 dom绘制。

useLayoutEffect在js线程执行完毕即dom更新之后立即执行,而useEffect是在渲染结束后才执行,也就是说 useLayoutEffect比 useEffect先执行。

♥︎♥︎♥︎ hooks 的使用有什么注意事项

(1)只能在React函数式组件或自定义Hook中使用Hook。

(2)不要在循环,条件或嵌套函数中调用Hook,必须始终在React函数的顶层使用Hook。这是因为React需要利用调用顺序来正确更新相应的状态,以及调用相应的钩子函数。一旦在循环或条件分支语句中调用Hook,就容易导致调用顺序的不一致性,从而产生难以预料到的后果。

♥︎♥︎♥︎ 纯函数有什么特点,副作用函数特点

纯函数与外界交换数据只有一个唯一渠道——参数和返回值;函数从函数外部接受的所有输入信息都通过参数传递到该函数内部;函数输出到函数外部的所有信息都通过返回值传递到该函数外部。

纯函数的优点:无状态,线程安全;纯函数相互调用组装起来的函数,还是纯函数;应用程序或者运行环境可以对纯函数的运算结果进行缓存,运算加快速度。

函数副作用是指当调用函数时,除了返回函数值之外,还对主调用函数产生附加的影响。比如调接口、修改全局变量、抛出一个异常或以一个错误终止、打印到终端或读取用户输入、读取或写入一个文件等,所以说副作用是编程中最关键的部分,因为我们需要跟用户、跟数据进行交互。

♥︎♥︎♥︎ 在构造函数调用 super 并将 props 作为参数传入的作用是啥?

ES6 中在调用 super()方法之前,子类构造函数无法使用this引用,在react的类组件中也是如此;将props 参数传递给 super() 调用的主要原因是在子构造函数中能够通过this.props来获取传入的 props。

♥︎♥︎♥︎ 讲讲什么是 JSX ?

JSX全称为JavaScript XML,是react中的一种语法糖,可以让我们在js代码中脱离字符串直接编写html代码;本身不能被浏览器读取,必须使用@babel/preset-react和webpack等工具将其转换为传统的JS。

主要有以下特点:

(1)类XML语法容易接受,结构清晰;

(2)增强JS语义;

(3)抽象程度高,屏蔽DOM操作,跨平台;

(4)代码模块化;

♥︎♥︎♥︎ 为什么不直接更新 state 呢?

如果试图直接更新 state ,则不会重新渲染组件;需要使用setState()方法来更新 state这样组件才会重新渲染;

♥︎♥︎♥︎ 这三个点(…)在 React 干嘛用的?

…是es6语法新出的规范,叫做展开运算符;在react中可以将对象或数组进行展开,让我们操作改变数据结构非常方便。

♥︎♥︎♥︎ React 中的 useState() 是什么?

useState是一个内置的React Hook,可以让我们在函数组件中像类组件一样使用state并且改变state的值。

♥︎♥︎♥︎ React 中的StrictMode(严格模式)是什么?

React的StrictMode是一种辅助组件,用包装组件,可以帮助我们编写更好的react组件,不会渲染出任何可见的ui;仅在开发模式下运行,它们不会影响生产构建,可以做以下检查:

(1)验证内部组件是否遵循某些推荐做法,如果没有,会在控制台给出警告;

(2)验证是否使用的已经废弃的方法,如果有,会在控制台给出警告;

(3)通过识别潜在的风险预防一些副作用。

♥︎♥︎♥︎ 为什么类方法需要绑定到类实例?

在 JS 中,this 值会根据当前上下文变化。在 React 类组件方法中,开发人员通常希望 this 引用组件的当前实例,因此有必要将这些方法绑定到实例。通常这是在构造函数中完成的:

♥︎♥︎♥︎ 这段代码有什么问题吗?

this.setState((prevState, props) => {
 return {
 streak: prevState.streak + props.count
 }
})
// 没有问题

♥︎♥︎♥︎ 如何在 React 的 Props 上应用验证?

1、使用PropTypes进行类型检查

PropTypes自React v15.5起,请使用这个库prop-types

2、What & Why & When

随着应用的不断增长,也是为了使程序设计更加严谨,我们通常需要对数据的类型(值)进行一些必要的验证。出于性能方面的考虑,propTypes仅在开发模式下进行检测,在程序运行时就能检测出错误,不能使用到用户交互提醒用户操作错误等,也可以使用Flow或者TypeScript做类型检查,后期建议用typescript进行替代更好

3、Where

  • class组件
  • 函数组件
  • React.memo高阶组件 可自行扩展
  • React.forwardRef组件 可自行扩展

4、How

我们在组件类下添加一个静态属性 propTypes (属性名不能更改),它的值也是一个对象,用来设置组件中props的验证规则,key 是要验证的属性名称,value 是验证规则。

// 类组件
import PropTypes from 'prop-types';
class Greeting extends React.Component {
 render() {
 return ()
 }

♥︎♥︎♥︎ 如何有条件地向 React 组件添加属性?

对于某些属性,React足够智能可以忽略该属性,比如值为boolean值属性的值也可以写控制语句管理是否给组件添加属性

♥︎♥︎♥︎ 如何避免组件的重新渲染?

  1. 当porps/state改变时组件会执行render函数也就是重新渲染
  2. class组件中使用shouldComponentUpdate钩子函数
  3. PureComponent默认有避免重新渲染的功能
  4. 函数组件使用高阶组件memo处理

♥︎♥︎♥︎ 什么是纯函数?

一个不会更改入参,且多次调用下相同的入参始终返回相同的结果

♥︎♥︎♥︎ 如何避免在React重新绑定实例?

  1. 将事件处理程序定义为内联箭头函数
  2. 使用箭头函数来定义方法
  3. 使用带有 Hooks 的函数组件

♥︎♥︎♥︎ 在js原生事件中 onclick 和 jsx 里 onclick 的区别

1、js原生中

onclick添加事件处理函数是在全局环境下执行,污染了全局环境,且给很多dom元素添加onclick事件,影响网页的性能,同时如果动态的从dom树种删除了该元素,还要手动注销事件处理器,不然就可能造成内存泄露

2、jsx里的onClick

挂载的函数都控制在组件范围内,不会污染全局空间

jsx中不是直接使用onclick,而是采取了事件委托的方式,挂载最顶层DOM节点,所有点击事件被这个事件捕获,然后根据具体组件分配给特定函数,性能当然比每个onClick都挂载一个事件处理函数要高,加上React控制了组件的生命周期,在unmount的时候自然能够清除相关的所有事件处理函数,内存泄露不再是一个问题

♥︎♥︎♥︎ React组件间信息传递

1.(父组件)向(子组件)传递信息 : porps传值

2.(父组件)向更深层的(子组件) 进行传递信息 : context

3.(子组件)向(父组件)传递信息:callback

4.没有任何嵌套关系的组件之间传值(比如:兄弟组件之间传值): 利用共同父组件context通信、自定义事件

5.利用react-redux进行组件之间的状态信息共享 : 组件间状态信息共享:redux、flux、mobx等

♥︎♥︎♥︎ React状态管理工具有哪些?redux actionCreator都有什么?

简单状态管理:组件内部state、基于Context API封装

复杂状态管理:redux(单项数据流)、mobx(响应式数据流)、RxJS(stream)、dva

创建各种action,包含同步、异步,然后在组件中通过dispatch调用

♥︎♥︎♥︎ vuex 和 redux 的区别?

vuex的流向:

view——>commit——>mutations——>state变化——>view变化(同步操作)
view——>dispatch——>actions——>mutations——>state变化——>view变化(异步操作)

redux的流向:

view——>actions——>reducer——>state变化——>view变化(同步异步一样)

Redux相对于Flux的改进:

(1)把store和Dispatcher合并,结构更加简单清晰

新增state角色,代表每个时间点store对应的值,对状态的管理更加明确

Redux数据流的顺序是:

(2)View调用store.dispatch发起Action->store接受Action(action传入reducer函数,reducer函数返回一个新的state)->通知store.subscribe订阅的重新渲染函数

Vuex是专门为Vue设计的状态管理框架, 同样基于Flux架构,并吸收了Redux的优点

Vuex相对于Redux的不同点有:

(1)改进了Redux中的Action和Reducer函数,以mutations变化函数取代Reducer,无需switch,只需在对应的mutation函数里改变state值即可

(2)由于Vue自动重新渲染的特性,无需订阅重新渲染函数,只要生成新的State即可

(3)Vuex数据流的顺序是:View调用store.commit提交对应的请求到Store中对应的mutation函数->store改变(vue检测到数据变化自动渲染)

♥︎♥︎♥︎ Redux遵循的三个原则是什么?

1、单一数据源

整个应用的state被存储在一棵object tree中,并且整个 object tree 只存在于唯一一个 store 中

2、State是只读的

唯一改变state的方法就是触发 action,action是一个描述已发生事件的普通对象,这样确保视图和网络请求不能直接修改state

3、使用纯函数来执行修改

为了描述action如何改变state tree,你需要编写reducers

♥︎♥︎♥︎ React中的keys的作用是什么?

key 是用来帮助 react 识别哪些内容被更改、添加或者删除。key 需要写在用数组渲染出来的元素内部,并且需要赋予其一个稳定的值。稳定在这里很重要,因为如果 key 值发生了变更,react 则会触发 UI 的重渲染。这是一个非常有用的特性。

1、key 的唯一性

在相邻的元素间,key 值必须是唯一的,如果出现了相同的 key,同样会抛出一个 Warning,告诉相邻组件间有重复的 key 值。并且只会渲染第一个重复 key 值中的元素,因为 react 会认为后续拥有相同key 的都是同一个组件。

2、key 值不可读

虽然我们在组件上定义了 key,但是在其子组件中,我们并没有办法拿到 key 的值,因为 key 仅仅是给react 内部使用的。如果我们需要使用到 key 值,可以通过其他方式传入,比如将 key 值赋给 id 等

♥︎♥︎♥︎ redux中使用setState不能立刻获取值,怎么办

setState 只在合成事件和钩子函数中是异步的,在原生事件和 setTimeout 中都是同步

①addeventListener添加的事件或者dom事件中触发

②setState接收的参数还可以是一个函数,在这个函数中可以拿先前的状态,并通过这个函数的返回值得到下一个状态。

 this.setState((preState) => {
 return {
 xxx: preState.xxx + yyy
 }
})

③async/await 异步调用处理

♥︎♥︎♥︎ React新老版生命周期函数

New Version

挂载:constructor –> getDerivedStateFromProps –> render –> componentDidMount

更新:

setState() –> getDerivedStateFromProps –> shouldComponentUpdate –> render –>getSnapshotBeforeUpdate –> componentDidUpdate

forceUpdate() –> getDerivedStateFromProps –> render –> getSnapshotBeforeUpdate –>componentDidUpdate

卸载: componentWillUnmount

Old Version

挂载:constructor –> getDerivedStateFromProps –> render –> ComponentDidMount

更新:

New props –> getDerivedStateFromProps –> shouldComponentUpdate –> render –>getSnapshotBeforeUpdate –> componentDidUpdate

setState() –> shouldComponentUpdate –> render –> getSnapshotBeforeUpdate –>componentDidUpdate

forceUpdate() –> render –> getSnapshotBeforeUpdate –> componentDidUpdate

卸载:componentWillUnmount

♥︎♥︎♥︎ React中怎么让 setState 同步更新?

setState 回调,setState,第二个参数是一个回调函数,可实现同步

引入 Promise 封装 setState,在调用时我们可以使用 Async/Await 语法来优化代码风格

setStateAsync(state) {
 return new Promise((resolve) => {
 this.setState(state, resolve)
 });
}

//传入状态计算函数, setState 的第一个参数,
this.setState((prevState, props) => ({ count: prevState.count + 1
}));

//在 setTimeout 函数中调用 setState

♥︎♥︎♥︎ 为什么不建议在 componentWillMount 做AJAX操作

Fiber原因,React16之后,采用了Fiber架构,只有componentDidMount的生命周期函数确定会执行一次,其他像componentWillMount可能会执行多次

render 阶段 可能会被React暂停,中止或重启

♥︎♥︎♥︎ 怎么用useEffect模拟生命周期函数?

  • 默认函数组件没有生命周期
  • 函数组件是一个纯函数,执行完即销毁,自己无法实现生命周期
  • 通过Effect hook把生命周期“钩”到纯函数中
    // 模拟 class 组件的 DidMount 和 DidUpdate
    useEffect(() => {
      console.log('在此发送一个 ajax 请求')
    })
 
    // 模拟 class 组件的 DidMount
     useEffect(() => {
        console.log('加载完了')
     }, []) // 第二个参数是 [] (不依赖于任何 state)
 
     // 模拟 class 组件的 DidUpdate
     useEffect(() => {
         console.log('更新了')
     }, [count, name]) // 第二个参数就是依赖的 state
 
    // 模拟 class 组件的 DidMount
    useEffect(() => {
        let timerId = window.setInterval(() => {
            console.log(Date.now())
        }, 1000)
 
        // 返回一个函数
        // 模拟 WillUnMount 组件销毁的时候 停止计时器
        return () => {
            window.clearInterval(timerId)
        }
    }, [])
  • 模拟componentDidMount - useEffect 依赖 [ ]
  • 模拟compenentDidUpdate - useEffect 无依赖 ,或者 依赖 [a,b,c]
  • 模拟componentWillUnMount - useEffect 中返回一个函数

♥︎♥︎♥︎ 各种useEffect使用情况?

1、默认情况下,它在第一次渲染之后和每次更新之后都会执行,无需清除的effect

// 在函数式组件中 在 return之前
// Similar to componentDidMount and componentDidUpdate:
 useEffect(() => {
 // Update the document title using the browser API
 document.title = You clicked ${count} times ;
 });

2、需要清除的effect:React 会在组件卸载的时候执行清除操作

 useEffect(() => {
 function handleStatusChange(status) {
 setIsOnline(status.isOnline);
 }
 ChatAPI.subscribeToFriendStatus(props.friend.id, handleStatusChange);
 // Specify how to clean up after this effect:
 return function cleanup() {
 ChatAPI.unsubscribeFromFriendStatus(props.friend.id, handleStatusChange);
 };
 });

3、使用多个effect实现关注点的分离

把类组件中的分散在多个生命周期中的同一件事件的处理,合并到同一个effect中处理

4、通过跳过effect进行性能优化

useEffect(() => {
 document.title = You clicked ${count} times ;
}, [count]); // 仅在 count 更改时更新

♥︎♥︎♥︎ useCallback是干什么的?使用useCallback有什么好处?

把内联回调函数及依赖项数组作为参数传入 useCallback,它将返回该回调函数的 memoized(缓存)版本,该回调函数仅在某个依赖项改变时才会更新 好处 当你把回调函数传递给经过优化的并使用引用相等性去避免非必要渲染(例如 shouldComponentUpdate)的子组件时,它将非常有用

♥︎♥︎♥︎ 能简单说一下redux-sage的使用流程吗?

redux-saga 是一个用于管理 Redux 应用异步操作的中间件(又称异步 action)。 redux-saga 通过创建 Sagas 将所有的异步操作逻辑收集在一个地方集中处理,可以用来代替 redux-thunk 中间件。

Reducers 负责处理 action 的 state 更新

Sagas 负责协调那些复杂或异步的操作

1、connet to the store:本质是管理 Redux 应用异步操作的中间件

import { createStore, applyMiddleware } from 'redux'
import createSagaMiddleware from 'redux-saga'
import reducer from './reducers'
import mySaga from './sagas'
// Create the saga middleware
const sagaMiddleware = createSagaMiddleware()
// Mount it on the Store
const store = createStore(
 reducer,
 applyMiddleware(sagaMiddleware)
)
// Then run the saga
sagaMiddleware.run(mySaga)
// Render the application

2、initiate a side effect:初始化副作用

import { call, put, takeEvery, takeLatest } from 'redux-saga/effects'
import Api from '...'
// Worker saga will be fired on USER_FETCH_REQUESTED actions
function* fetchUser(action) {
 try {
 const user = yield call(Api.fetchUser, action.payload.userId);
 yield put({type: "USER_FETCH_SUCCEEDED", user: user});
 } catch (e) {
 yield put({type: "USER_FETCH_FAILED", message: e.message});
 }
}
// Starts fetchUser on each dispatched USER_FETCH_REQUESTED action
// Allows concurrent fetches of user
function* mySaga() {
 yield takeEvery("USER_FETCH_REQUESTED", fetchUser);
}

3、dispath an action:组件中使用

 ...
 onSomeButtonClicked() {
 const { userId, dispatch } = this.props
 dispatch({type: 'USER_FETCH_REQUESTED', payload: {userId}})
 }
 ...
}

4、more:takeEvery、takeLatest、take、put、call、fork、select

♥︎♥︎♥︎ redux 和 mobx 的区别

1、Redux的编程范式是函数式的而Mobx是面向对象的

2、因此数据上来说Redux理想的是immutable的,每次都返回一个新的数据,而Mobx从始至终都是一份引用。因此Redux是支持数据回溯的

3、然而和Redux相比,使用Mobx的组件可以做到精确更新,这一点得益于Mobx的observable;对应的,Redux是用dispatch进行广播,通过Provider和connect来比对前后差别控制更新粒度,有时需要自己写SCU;Mobx更加精细一点

4、Mobx-react vs React-rdux:

redux,采取Provider和connect方式,mobx采取Provider和inject、observer

♥︎♥︎♥︎ react中如何实现命名插槽

由于在React组件中写的内容会被挂载到props中,以此来实现类似vue中的插槽功能

这是最外层代码

import React, { Component } from 'react'
import NavBar from './NavBar'
import NavBar2 from './NavBar2'

export default class App extends Component {
  render() {
    return (
      <div>
        <NavBar>
          <span>aaa</span>
          <strong>bbb</strong>
          <a href="/#">ccc</a>
        </NavBar>

        <NavBar2 leftslot={<span>aaa</span>}
        centerslot={<strong>bbb</strong>}
        rightslot={<a href="/#">ccc</a>}/>
      </div>
    )
  }
}

1.用this.props.children[index]

import React, { Component } from 'react'

import './style.css'
export default class NavBar extends Component {
  render () {
    return (
      <div className="nav-bar">
        <div className="nav-left">
          {this.props.children[0]}
        </div>
        <div className="nav-center">
          {this.props.children[1]}
        </div>
        <div className="nav-right">
          {this.props.children[2]}
        </div>
      </div>
    )
  }
}

2.用直接命名方式

import React, { Component } from 'react'

import './style.css'
export default class NavBar extends Component {
  render () {
    const {leftslot, centerslot,rightslot} = this.props
    return (
      <div className="nav-bar">
        <div className="nav-left">
          {leftslot}
        </div>
        <div className="nav-center">
          {centerslot}
        </div>
        <div className="nav-right">
          {rightslot}
        </div>
      </div>
    )
  }
}

♥︎♥︎♥︎ 简单说一下,如何在react中实现瀑布流加载?(左右两列的一个商品长列表)

根据红线,将数据分为两部分,然后根据两边的高度(哪边少往那边加内容)去渲染两个盒子,然后达到一个瀑布流的效果

import React, { Component,Fragment } from 'react';
import {connect} from'react-redux'
import Axios from '_axios@0.19.0@axios';
class Waterfall extends Component {
 constructor(props) {
 super(props);
 this.state = { 
 data:[],//整体的数据
 leftData:[],//左边的数据
 rightData:[]//右边的数据
 }
 }
 getHW(data){
 let heightDate = [0,0];//接收累计高度的容器数组
 let rightData =[]//渲染右侧盒子的数组
 let leftData = []//渲染左侧盒子的数组
 data.forEach(item => {
 let height = item.src.replace('http://dummyimage.com/','').substr(0,7).split('x')[1]*1;//对url地址进行一
个截取,拿到高度
 let minNum = Math.min.apply(null,heightDate)// 从heighetData筛选最小项
 let minIndex = heightDate.indexOf(minNum);// 获取 最小项的小标 准备开始进行累加
 heightDate[minIndex] = heightDate[minIndex] + height;//从 heightData 中找到最小的项后进行累加,
 if(minIndex===0){//[0]加到left [1]加到 right
 leftData.push(item)
 }else{
 rightData.push(item)
 }
 })
 this.setState({ leftData,rightData });//重新set state
 }
 render() { 
 let {leftData,rightData} = this.state;
 console.log(leftData,rightData)
 return ( 
 
 
{ leftData && leftData.map((item,index)=>{ return }) } { rightData && rightData.map((item,index)=>{ return }) }
); } componentDidMount(){ Axios.get('/api/data').then(res=>{ this.props.dispatch({ type:'SET_DATA',
data:res.data.data }) this.getHW(this.props.data) //调用 }) } }
export default connect(
 (state)=>{
 return{
 data:state.data,
 }
 }
)(Waterfall);

♥︎♥︎♥︎♥︎ React hooks 用过吗,为什么要用?

Hooks 是React在16.8版本中出的一个新功能,本质是一种函数,可以实现组件逻辑复用,让我们在函数式组件中使用类组件中的状态、生命周期等功能,hooks的名字都是以use开头。

1、useState——创建状态 接收一个参数作为初始值;返回一个数组,第一个值为状态,第二个值为改变状态的函数 
2、useEffect——副作用(数据获取、dom操作影响页面——在渲染结束之后执行)
        (1)第一个参数为函数,第二个参数为依赖列表,只有依赖更新时才会执行函数;返回一个函 数,当页面刷新的时候先执行返回函数再执行参数函数 
        (2)如果不接受第二个参数,那么在第一次渲染完成之后和每次更新渲染页面的时候,都会调 用useEffect的回调函数 
3、useRef 返回一个可变的ref对象,此索引在整个生命周期中保持不变。可以用来获取元素或组件的实例,用来做 输入框的聚焦或者动画的触发。 
4、useMemo——优化函数组件中的功能函数——在渲染期间执行 
    (1)接收一个函数作为参数,同样接收第二个参数作为依赖列表,返回值可以是任何,函数、对象等都可 以 
    (2)这种优化有助于避免在每次渲染时都进行高开销的计算,仅会在某个依赖项改变时才重新计算 
5、useContext——获取上下文注入的值 
        (1)接受一个context 对象,并返回该对象<MyContext.Provider> 元素的 value值; const value = useContext(MyContext); 
6、useLayoutEffect——有DOM操作的副作用——在DOM更新之后执行 和useEffet类似,但是执行时机不同,useLayoutEffect在DOM更新之后执行, useEffect在render渲染结束后执行,也就是说useLayoutEffect比useEffect先执行,这是因为DOM更 新之后,渲染才结束或者渲染还会结束 
7、useCallback——与useMemo类似
useMemo与useCallback相同,接收一个函数作为参数,也同样接收第二个参数作为依赖列 表;useCallback是对传过来的回调函数优化,返回的是一个函数
react-router:
 被route包裹的组件,可以直接使用props进行路由相关操作,但是没有被route包裹的组件只能用withRouter高阶组件修饰或者使用hooks进行操作
 1、useHistory——跳转路由
 2、useLocation——得到url对象
 3、useParams——得到url上的参数

react-redux:
 1、useSelector——共享状态——从redux的store中提取数据
 2、useDispatch——共享状态——返回redux的store中对dispatch的引用

♥︎♥︎♥︎♥︎ 虚拟DOM的优劣如何?实现原理?

虚拟dom是用js模拟一颗dom树,放在浏览器内存中,相当于在js和真实dom中加了一个缓存,利用dom diff算法避免了没有必要的dom操作,从而提高性能。

优点:

(1)虚拟DOM具有批处理和高效的Diff算法,最终表现在DOM上的修改只是变更的部分,可以保证非常高效的渲染,优化性能;

(2)虚拟DOM不会立马进行排版与重绘操作,对虚拟DOM进行频繁修改,最后一次性比较并修改真实DOM中需要改的部分;

(3)虚拟 DOM 有效降低大面积真实 DOM 的重绘与排版,因为最终与真实 DOM 比较差异,可以只渲染局部;

缺点:

(1)首次渲染大量DOM时,由于多了一层虚拟DOM的计算,会比innerHTML插入慢;

React组件的渲染过程: 
(1)使用JSX编写React组件后所有的JSX代码会通过Babel转化为 React.createElement执行; 
(2)createElement函数对 key和 ref等特殊的 props进行处理,并获取 defaultProps对默认 props进行赋值,并且对传入的子节点进行处理,最终构造成一个 ReactElement对象(所谓的虚拟 DOM)。
(3)ReactDOM.render将生成好的虚拟 DOM渲染到指定容器上,其中采用了批处理、事务等机制并且 对特定浏览器进行了性能优化,最终转换为真实 DOM。

虚拟DOM的组成——ReactElementelement对象结构: 
(1)type:元素的类型,可以是原生html类型(字符串),或者自定义组件(函数或class) 
(2)key:组件的唯一标识,用于Diff算法,下面会详细介绍 
(3)ref:用于访问原生dom节点 
(4)props:传入组件的props,chidren是props中的一个属性,它存储了当前组件的孩子节点,可 以是数组(多个孩子节点)或对象(只有一个孩子节点) 
(5)owner:当前正在构建的Component所属的Component 
(6)self:(非生产环境)指定当前位于哪个组件实例 
(7)_source:(非生产环境)指定调试代码来自的文件(fileName)和代码行数(lineNumber)

♥︎♥︎♥︎♥︎ React 和 Vue 的 diff 时间复杂度从 O(n^3) 优化到 O(n) ,那么 O(n^3) 和 O(n) 是如何计算出来的?

react的diff算法只需要O(n),这是因为react对树节点的比较做了一些前提假设,限定死了一些东西,不做过于复杂的计算操作,所以降低了复杂度。react和vue做了以下的假设,这样的话diff运算时只进行同层比较,每一个节点只遍历了一次。

(1)Web UI中DOM节点跨层级的移动操作特别少,可以忽略不计;

(2)拥有相同类的两个组件将会生成相似的树形结构,拥有不同类的两个组件将会生成不同的树形结构;

(3)对于同一层级的一组子节点,它们可以通过唯一 id 进行区分。

而传统的diff运算时间复杂度为O(n^3),这是因为传统的树节点要做非常完整的检查,首先需要节点之 间需要两两比较,找到所有差异,这个对比过程时间复杂度为O(n^2),找到差异后还要计算出最小的转换方式,最终复杂度为O(n^3)

♥︎♥︎♥︎♥︎ 请列举react生命周期函数。

阶段 生命周期
第一阶段:装载阶段3 constructor() render() componentDidMount()
第二阶段:更新阶段2 shouldComponentUpdate() render() componentDidUpdate()
第三阶段:卸载阶段1 componentWillUnmount()

constructor生命周期:

(1)当react组件实例化时,是第一个运行的生命周期;

(2)在这个生命周期中,不能使用this.setState();

(3)在这个生命周期中,不能使用副作用(调接口、dom操作、定时器、长连接等);

(4)不能把props和state交叉赋值;

componentDidMount生命周期:

(1)相当于是vue中的mounted;

(2)它表示DOM结构在浏览器中渲染已完成;

(3)在这里可以使用任何的副作用;

shouldComponentUpdate(nextProps,nextState)生命周期:

(1)相当于一个开关,如果返回true则更新机制正常执行,如果为false则更新机制停止;

(2)在vue中是没有的;

(3)存在的意义:可以用于性能优化,但是不常用,最新的解决方案是使用PureComponent;

(4)理论上,这个生命周期的作用,用于精细地控制声明式变量的更新问题,如果变化的声明式变量参与了视图渲染则返回true,如果被变化的声明式变量没有直接或间接参与视图渲染,则返回false;

componentDidUpdate生命周期:

(1)相当于vue中的updated();

(2)它表示DOM结构渲染更新已完成,只发生在更新阶段;

(3)在这里,可以执行大多数的副作用,但是不建议;

(4)在这里,可以使用this.setState(),但是要有终止条件判断。

componentWillUnmount生命周期:

(1)一般在这里清除定时器、长连接等其他占用内存的构造器;render生命周期:

(1)render是类组件中唯一必须有的生命周期,同时必须有return(return 返回的jsx默认只能是单一根节点,但是在fragment的语法支持下,可以返回多个兄弟节点);

(2)Fragment碎片写法: <React.Fragment></React.Fragment> 简写成<></>;

(3)return之前,可以做任意的业务逻辑,但是不能使用this.setState(),会造成死循环;

(4)render()在装载阶段和更新阶段都会运行;

(5)当render方法返回null的时候,不会影响生命周期函数的正常执行。

♥︎♥︎♥︎♥︎ 新出来两个钩子函数?和砍掉的will系列有啥区别?

/ react16 中废弃了三个钩子
componentWillMount // 组件将要挂载的钩子
componentWillReceiveProps // 组件将要接收一个新的参数时的钩子
componentWillUpdate // 组件将要更新的钩子
/ 新增了方法
getDerivedStateFromProps // 静态方法
getSnapshotBeforeUpdate

在16.8版本以后,react将diff运算改进为Fiber,这样的话当我们调用setState方法进行更新的时候,在reconciler 层中js运算会按照节点为单位拆分成一个个小的工作单元,在render前可能会中断或恢复,就有可能导致在render前这些生命周期在进行一次更新时存在多次执行的情况,此时如果我们在里面使用ref操作dom的话,就会造成页面频繁重绘,影响性能。 所以废弃了这几个will系列的勾子,增加了 getDerivedStateFromProps这个静态方法,这样的话我们就不能在其中使用this.refs以及this上的方法了;getSnapshotBeforeUpdate 这个方法已经到了commit阶段,只会执行一次,给想读取 dom 的用户一些空间。

♥︎♥︎♥︎♥︎ react中 setState 之后做了什么?

如果是在隶属于原生js执行的空间,比如说setTimeout里面,setState是同步的,那么每次执行setState将立即更新this.state,然后触发render方法,渲染数据;

如果是在被react处理过的空间执行,比如说合成事件里,此时setState是异步执行的,并不会立即更新this.state的值,当执行setState的时候,会将需要更新的state放入状态队列,在这个空间最后再合并修改this.state,触发render;

♥︎♥︎♥︎♥︎ redux本来是同步的,为什么它能执行异步代码?中间件的实现原理是什么?

当我们需要修改store中值的时候,我们是通过 dispatch(action)将要修改的值传到reducer中的,这个过程是同步的,如果我们要进行异步操作的时候,就需要用到中间件;中间件其实是提供了一个分类处理action的机会,在 middleware 中,我们可以检阅每一个流过的action,并挑选出特定类型的action进行相应操作,以此来改变 action;

applyMiddleware 是个三级柯里化的函数。它将陆续的获得三个参数:第一个是 middlewares 数 组,第二个是 Redux 原生的 createStore,最后一个是 reducer;然后applyMiddleware会将不同的 中间件一层一层包裹到原生的 dispatch 之上; 
redux-thunk 中间件的作用就是让我们可以异步执行redux,首先检查参数 action 的类型,如果 是函数的话,就执行这个 action这个函数,并把 dispatch, getState, extraArgument 作为参数传 递进去,否则就调用next让下一个中间件继续处理action。
// redux-thunk部分源码
function createThunkMiddleware(extraArgument) {
 return ({ dispatch, getState }) => next => action => {
 if (typeof action === 'function') {
 return action(dispatch, getState, extraArgument)
 }
 return next(action)
 }
}
const thunk = createThunkMiddleware()
thunk.withExtraArgument = createThunkMiddleware
export default thunk

♥︎♥︎♥︎♥︎ 列举重新渲染 render 的情况

  1. this.setState()
  2. this.forceUpdate()
  3. 接受到新的props
  4. 通过状态管理,mobx、redux等
  5. 改变上下文

♥︎♥︎♥︎♥︎ 类组件怎么做性能优化?函数组件怎么做性能优化?

类组件:

 (1)使用shouldComponentUpdate:这个生命周期可以让我们决定当前状态或属性的改变是否重新渲染组件,默认返回ture,返回false时不会执行render,在初始化渲染或使用forceUpdate()时不会调用;如果在shouldComponentUpdate比较的值是引用类型的话,可能达不到我们想要的效果,因为引用类型指向同一个地址;当将旧的state的值原封不动赋值给新的state(即不改变state的值,但是调用了setState)和 无数据交换的父组件的重新渲染都会导致组件重新渲染,这时候都可以通过shouldComponentUpdate来优化;

 (2)React.PureComponent:基本上和Component用法一致,不同之处在于 PureComponent不需要开发者自己设置shouldComponentUpdate,因为PureComponent自带通过props和state的浅对比来实现 shouldComponentUpate;但是如果props和state对象包含复杂的数据结构,它可能会判断错误(表现为对象深层的数据已改变,视图却没有更新);

 (4)使用Immutable:immutable是一种持久化数据,一旦被创建就不会被修改,修改immutable对象的时候返回新的immutable;也就是说在使用旧数据创建新数据的时候,会保证旧数据同时可用且不变;为了避免深度复制所有节点的带来的性能损耗,immutable使用了结构共享,即如果对象树中的一个节点发生变化,只修改这个节点和受他影响的父节点,其他节点仍然共享;

 (5)bind函数:在react中改变this的指向有三种方法,a)constructor中用bind绑定; b)使用时通过bind绑定; c)使用箭头函数;选择第一种只在组件初始化的时候执行一次,第二种组件在每次render都要重新绑定,第三种在每次render时候都会生成新的箭头函数,所以选择第一种;

函数组件:

 (1)useCallback:接收一个函数作为参数,接收第二个参数作为依赖列表,返回值为函数,有助于避免在每次渲染时都进行高开销的计算,仅会在某个依赖项改变时才重新计算;可以使用useCallback把要传递给子组件的函数包裹起来,这样父组件刷新的时候,传递给子组件的函数指向不会发生改变,可以减少子组件的渲染次数;

 const handleUseCallback=useCallback(handleClick,[])

 (2)useMemo:useMemo的使用和useCallback差不多,只是useCallback返回的是一个函数,useMemo返回值可以是函数、对象等都可以;

两者都可使用:

(1)React.memo:React.memo 功能同React.PureComponent,但React.memo是高阶组件,既可以用在类组件中也可以用在函数组件中;memo还可以接收第二个参数,是一个可定制化的比较函数,其返回值与 shouldComponentUpdate的相反;

(2)使用key:在列表渲染时使用key,这样当组件发生增删改、排序等操作时,diff运算后可以根据key值直接调整DOM顺序,避免不必要的渲染而避免性能的浪费;

(3)不要滥用props:尽量只传需要的数据,避免多余的更新,尽量避免使用{…props};

♥︎♥︎♥︎♥︎ 什么是 prop drilling,如何避免?

从一个外部组件一层层将prop传递到内部组件很不方便,这个问题就叫做 prop drilling;主要缺点是原本不需要数据的组件变得不必要地复杂,并且难以维护,代码看起来也变得冗余,不优雅;

为了避免prop drilling,一种常用的方法是使用React Context。通过定义提供数据的Provider组件,并允许嵌套的组件通过 Consumer组件或 useContext Hook 使用上下文数据。

♥︎♥︎♥︎♥︎ 什么是 React Context?

React Context源码解析

1、What

Context提供了一个无需为每层组件手动添加props,就能在组件树间进行数据传递的功能

2、Why

某些全局属性,通过父子props传递太过繁琐,Context提供了一种组件之间共享此类值的方式,而不必显式的通过组件树逐层传递props

3、When

共享那些对于一个组件树而言是全局的数据,例如当前认证的用户、主题或者首选语言等

4、Where

Context应用场景在于很多不同层级的组件访问同样的数据,这样也使得组件的复用性变差。

如果你只是想避免层层传递一些属性,组件组合有时候是一个比Context更好的方案,也就是直接传递组件

所以一个技术方案的选定需要针对不同的场景具体分析,采取合适的方案

5、How

// ①创建

const ThemeContext = React.createContext('xxx')

// ②注入---提供者 在入口或者你想要注入的父类中,且可以嵌套,里层覆盖外层

return (
<ThemeContext.Provider value="yyy">
{children}
<ThemeContext.Provider>
)

// ③使用---消费者 需要使用共享数据的子类中

// 方式一
static contextType = ThemeContext
// 方式二
Class.contextType = ThemeContext 
render() {
let value = this.context
/* 基于这个值进行渲染工作 */ 
}
//方式三
return(
<ThemeContext.Consumer>
{ value => /* 基于 context 值进行渲染*/ }
</ThemeContext.Consumer>
)

6、More

动态Context—类似父子组件

// ①创建

const ThemeContext = React.createContext({
value: 'xxx',
changeFunc: () => {} /*通过context传递这个函数,让consumers组件更新context*/
})

// ②注入
return (
<ThemeContext.Provider value="yyy">
<ThemeContext.Provider>
)

// ③消费

return(
<ThemeContext.Consumer>
{ ({value, changeFunc}) => /* 基于 context 值进行渲染,同时把changeFunc绑定*/ }
 </ThemeContext.Consumer>
)
消费多个Context、注意事项等参考React中文网

♥︎♥︎♥︎♥︎ Hooks 会取代 render props 和高阶组件吗?

可以取代,但没必要

在Hook的渐进策略中也有提到,没有计划从React中移除class,在新的代码中同时使用Hook和class,所以这些方案目前还是可以有勇武之地

1、What

为什么要把这3种技术拿过来对比?

都在处理同一个问题,逻辑复用

高阶组件HOC—不是 React API 的一部分,是基于 React 的组合特性形成的设计模式。

高阶组件是参数为组件,返回值为新组件的函数(将组件转换为另一个组件,纯函数,无副作用)

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

Hooks 是React16.8新增的特性,是一些可以让你在函数组件里“钩入”React state及生命周期等特性的函数

虽然 HOC & Render Props 能处理逻辑复用的问题,但是却存在各自的问题。

HOC 存在的问题

  • 写法破坏了原来组件的结构,DevTools中组件会形成“嵌套地狱”
  • 不要在 render 方法中使用 HOC 每次调用render函数会创建一个新的高阶组件导致该组件及其子组件的状态丢失
  • 需要修复静态方法,即拷贝原组件的静态方法到高级组件中
  • 如需传递Ref则需要通过React.forwardRef创建组件

Render Props 存在的问题

  • 同样的写法会破坏原来组件的结构,DevTools中组件会形成“嵌套地狱”
  • 与React.PureComponent组件使用有冲突
  • Hook 目前最优雅的实现,React为共享状态逻辑提供最好的原生途径
  • 没有破坏性改动,完全可选,100%向后兼容
  • 解决复杂组件,中逻辑状态、副作用和各种生命周期函数中逻辑代码混在一起,难以拆分,甚至形成bug的问题

2、When

  • 在函数组件中意识到要向其添加一些state—useState
  • 有副作用的行为时

3、Where

只能在函数最外层调用Hook,不要在循环、条件判断或者子函数中调用

只能在函数组件或者自定义Hook中调用Hook

♥︎♥︎♥︎♥︎ 当调用setState时,React render 是如何工作的?

调用setState()

  1. 检查上下文环境生成更新时间相关参数并判定事件优先级(fiber,currenttime,expirationtime等…)
  2. 根据优先级相关参数判断更新模式是sync(同步更新)或是batched(批量处理)
  3. 加入执行更新事件的队列,生成事件队列的链表结构
  4. 根据链表顺序执行更新
  5. setState既是同步的,也是异步的。同步异步取决于setState运行时的上下文。且setState 只在合成事件和钩子函数中是“异步”的,在原生DOM事件和 setTimeout 中都是同步的

render如何工作

  1. React在props或state发生改变时,会调用React的render方法,创建一颗不同的树
  2. React需要基于这两颗不同的树之间的差别来判断如何有效的更新UI diff算法,将两颗树完全比较更新的算法从O(n^3^),优化成O(n);
  3. 同层节点之间相互比较,不会跨节点比较
  4. 不同类型的节点,产生不同的树结构
  5. 设置key来指定节点在不同的渲染下保持稳定

♥︎♥︎♥︎♥︎ diff复杂度原理及具体过程画图

  • React 通过制定大胆的 diff 策略,将 O(n3) 复杂度的问题转换成 O(n) 复杂度的问题;
  • React 通过分层求异的策略,对 tree diff 进行算法优化;
  • React 通过相同类生成相似树形结构,不同类生成不同树形结构的策略,对 component diff 进行算法优化;
  • React 通过设置唯一 key的策略,对 element diff 进行算法优化;
  • 建议,在开发组件时,保持稳定的 DOM 结构会有助于性能的提升;
  • 建议,在开发过程中,尽量减少类似将最后一个节点移动到列表首部的操作,当节点数量过大或更新操作过于频繁时,在一定程度上会影响 React 的渲染性能。

♥︎♥︎♥︎♥︎ shouldComponentUpdate的作用是什么?

shouldComponentUpdate的作用

1、What

不常用的生命周期方法,能影响组件是否重新渲染

在更新阶段,当有new props 或者 调用了 setState()方法,在render方法执行前会执行到,默认返回值为true,如果返回false则不刷新组件

2、Why & When & Where

  1. 如果你知道在什么情况下组件不需要更新,你可以让其返回值为false跳过整个渲染过程
  2. 次方法仅作为 性能优化方式 而存在,不要企图靠此方法来阻止渲染,
  3. 大部分情况下,使用PureComponent代替手写shouldComponentUpdate,仅浅层对比
  4. 不建议在shoulComponentUpdate中进行深层或者使用JSON.stringify(),这样非常影响效率和性能
  5. 作为React组件中不常用的生命周期函数,能影响组件是否重渲染
  6. 建议做浅层次的比较,来优化性能,当然这里也可以用PureComponent组件代替
  7. 如果有较深层次的比较则可能会导致更严重的性能问题,因此在这种情况下不要靠手动管理组件的重新渲染来优化性能,要找其他方式

♥︎♥︎♥︎♥︎ 什么是高阶组件、受控组件及非受控组件?都有啥区别

1、定义

高阶组件HOC—不是 React API 的一部分,是基于 React 的组合特性形成的设计模式。

高阶组件是参数为组件,返回值为新组件的函数(将组件转换为另一个组件,纯函数,无副作用)

2、受控组件

在表单元素中,state是唯一数据源,渲染表单的React组件控制着用户输入过程中表单发生的操作。被React以这种方式控制取值的表单输入元素叫做受控组件

3、非受控组件

表单数据由DOM节点来处理,而不是用state来管理数据,一般可以使用ref来从DOM节点中获取表单数据

‼️区别

  • 受控组件和非受控组件是表单中的组件,高阶组件相当于对某个组件注入一些属性方法
  • 高阶组件是解决代码复用性问题产生的技术
  • 受控组件必须要有一个value,结合onChange来控制这个value,取值为event.target.value/event.target.checked
  • 非受控组件相当于操作DOM,一般有个defaultValue,通过onBlur触发响应方法

♥︎♥︎♥︎♥︎ vue react都怎么检测数据变化

React

React默认是通过比较引用的方式(diff)进行的,不精确监听数据变化,如果不优化可能导致大量不必要的VDOM重新渲染

16之前 componentWillReveiveProps 监听 props 变化

16之后 getDerivedStateFromProps 监听 props

Vue

vue监听变量变化依靠 watch Object.defineProperty,Vue通过“getter/setter”以及一些函数的劫持,能精确知道数据变化

♥︎♥︎♥︎♥︎ 什么是 immutable?为什么要使用它?

immutable是一种持久化数据。一旦被创建就不会被修改。修改immutable对象的时候返回新的immutable。但是原数据不会改变。

在Rudux中因为深拷贝对性能的消耗太大了(用到了递归,逐层拷贝每个节点)。 但当你使用immutable数据的时候:只会拷贝你改变的节点,从而达到了节省性能。

总结:immutable的不可变性

让纯函数更强大,每次都返回新的immutable的特性让程序员可以对其进行链式操作,用起来更方便。

因为在react中,react的生命周期中的setState()之后的shouldComponentUpdate()阶段默认返回true,所以会造成本组件和子组件的多余的render,重新生成virtual dom,并进行virtual dom diff,所以解决办法是我们在本组件或者子组件中的shouldComponentUpdate()函数中比较,当不需要render时,不render。

当state中的值是对象时,我们必须使用深拷贝和深比较!

如果不进行深拷贝后再setState,会造成this.state和nextState指向同一个引用,所以shouldComponentUpdate()返回值一定是false,造成state值改了,而组件未渲染(这里不管shouldComponentUpdate中使用的是深比较还是浅比较)。所以必须深拷贝。

如果不在shouldComponentUpdate中进行深比较,会造成即使state中的对象值没有改变,因为是不同的对象,而在shouldComponentUpdate返回true,造成不必要的渲染。

所以只能是深拷贝和深比较。

♥︎♥︎♥︎♥︎ React路由懒加载的实现

原理

webpack代码分割

React利用 React.lazy与import()实现了渲染时的动态加载

利用Suspense来处理异步加载资源时页面应该如何显示的问题

1.React.lazy

通过lazy() api来动态import需要懒加载的组件

import的组件目前只支持export default的形式导出

Suspense来包裹懒加载的组件进行加载,可以设置fallback现实加载中效果

React.lazy可以结合Router来对模块进行懒加载。

import { BrowserRouter as Router, Route, Switch } from 'react-router-dom';
import { Suspense, lazy } from 'react';
const Home = lazy(() => import('./routes/Home'))
const AnyComponent = lazy(() => import('./routes/AnyComponent'))
...
return (
 <Suspense fallback={
Loading...
}> ...

2.react-loadable

react-loadable是以组件级别来分割代码的,这意味着,我们不仅可以根据路由按需加载,还可以根据组件按需加载,使用方式和路由分割一样,只用修改组件的引入方式即可

// 路由懒加载(异步组件)
import Loadable from 'react-loadable';
//通用过场组件
const LoadingComponent = () => {
 return (
loading
) } ... export default (loader, loading=LoadingComponent) => { return Loadable({ loader, loading }) }
//Route中调用
import { BrowserRouter, Route } from 'react-router-dom'
const loadable from './loadable';
const AnyComponent = loadable(() => import('./AnyComponent'))
const Routes = () => (
);
export default Routes;

以下是老版中的方法

3.webpack配置中使用lazyload-loader

// webpack 配置中
module: {
 rules: [
 {
 test: /.(js|jsx)$/,,
 use: [
 'babel-loader',
 'lazyload-loader'
 ]
},
// 业务代码中
// 使用lazy! 前缀 代表需要懒加载的Router
 import Shop from 'lazy!./src/view/Shop';
 // Router 正常使用

4.import() webpack v2+

符合ECMAScript提议的import()语法,该提案与普通 import 语句或 require 函数的类似,但返回一个 Promise 对象

function component() {
 return import( /* webpackChunkName: "lodash" / 'lodash').then(_ => {
 var element = document.createElement('div');
 element.innerHTML = _.join(['Hello', 'webpack'], ' ');
 return element;
 }).catch(error => 'An error occurred while loading the component');
}
// 或者使用async
async function getComponent() {
 var element = document.createElement('div');
 const _ = await import(/ webpackChunkName: "lodash" */ 'lodash');
 element.innerHTML = _.join(['Hello', 'webpack'], ' ');
 return element;
}

5.requre.ensure webpack v1 v2

require.ensure([], function(require){
 var list = require('./list');
 list.show();
,'list');
const Foo = require.ensure([], () => {
 require("Foo");
}, err => {
 console.error("We failed to load chunk: " + err);
}, "chunk-name");
//react-router2 or 3

♥︎♥︎♥︎♥︎ React-router-dom内部是怎么样实现的,怎么做路由守卫?

1、总

react-router-dom利用了Context API,通过上下文对象将当前路由信息对象注入到 Router 组件中,所以 Router组件中 render() 渲染的内容就是 ContextAPI 提供的 Provider 组件,然后接收 Router 组件中的当前路由信息对象。 这样 Router 组件下的所有组件都能通过上下文拿到当前路由信息对象,即其中的Switch 、 Route 、 Link 、Redirect 等组件都可以拿到当前路由信息对象,然后通过改变当前路由信息来实现动态切换 Route 组件的渲染。

2、分

RouterContext:react-router使用context实现跨组件间数据传递,所以react-router定义了一个routerContext作为数据源,

Router:BrowserRouter和HashRouter将当前路由注入到上下文中,同时路由信息包含location、match、history

Route:路由规则,获取RouterContext的信息(location对象),获取path和component属性,判断path和当前的location是否匹配,如果匹配,则渲染component,否则返回null,不渲染任何内容

Switch:遍历所有子元素(Route),判断Route的path和location是否匹配,如果匹配,则渲染,否则不渲染

Redireact:未能配则重定向到指定页面

Link/NavLink: Link组件本质就是a标签,它修改了a标签的默认行为,当点击Link时,会导航到对应的路由,导致locaiton对象的改变,出发组件的更新

withRouter:对传入的组件进行加强,功能就是获取routerContext上面的信息,然后作为props传给需要加强的组件

3、怎么做路由守卫

路由里设置meta元字符实现路由拦截

React Router 4.0之前也像vue中一样有个钩子函数 onEnter 可实现

ReactRouter 4.0开始自己实现如下

// routerMap.js中
import Index from './page/index'
export default [
 { path:'/', name: 'App', component:Index, auth: true },
 ...
]
//入口文件 app.js中
import { BrowserRouter as Router, Switch } from "react-router-dom";
import FrontendAuth from "./FrontendAuth";
import routerMap from "./routerMap";
...
return (
 
 
)
// 高阶组件FrontendAuth 处理路由跳转,即路由守卫功能
//FrontendAuth.js
import React, { Component } from "react";
import { Route, Redirect } from "react-router-dom";
class FrontendAuth extends Component {
 // eslint-disable-next-line no-useless-constructor
 constructor(props) {
 super(props);
 }
 render() {
 const { routerConfig, location } = this.props;
 const { pathname } = location;
 const isLogin = sessionStorage.getItem("username");
 console.log(pathname, isLogin);
 console.log(location);
 // 如果该路由不用进行权限校验,登录状态下登陆页除外
 // 因为登陆后,无法跳转到登陆页
 // 这部分代码,是为了在非登陆状态下,访问不需要权限校验的路由
 const targetRouterConfig = routerConfig.find(
 (item) => item.path === pathname
 );
 console.log(targetRouterConfig);
 if (targetRouterConfig && !targetRouterConfig.auth && !isLogin) {
 const { component } = targetRouterConfig;
 return ;
 }
 if (isLogin) {
 // 如果是登陆状态,想要跳转到登陆,重定向到主页
 if (pathname === "/login") {
 return ;
 } else {
 // 如果路由合法,就跳转到相应的路由
 if (targetRouterConfig) {
 return (
 
 );
 } else {
 // 如果路由不合法,重定向到 404 页面
 return ;
 }
 }
 } else {
 // 非登陆状态下,当路由合法时且需要权限校验时,跳转到登陆页面,要求登陆
 if (targetRouterConfig && targetRouterConfig.auth) {
 return ;
 } else {
 // 非登陆状态下,路由不合法时,重定向至 404
 return ;
 }
 }
 }
}
export default FrontendAuth;

总结一下,实现路由守卫需要考虑到以下的问题:

未登录情况下,访问不需要权限校验的合法页面:允许访问

未登录情况下,访问需要权限校验的页面:禁止访问,跳转至登陆页

未登录情况下,访问所有的非法页面:禁止访问,跳转至 404

登陆情况下,访问登陆页面:禁止访问,跳转至主页

登陆情况下,访问除登陆页以外的合法页面:允许访问

登陆情况下,访问所有的非法页面:禁止访问,跳转至 404

♥︎♥︎♥︎♥︎ redux中sages和thunk中间件的区别,优缺点

1、区别

  • redux-thunk异步采取 async/await
  • redux-saga采取generate函数

2、优缺点

redux-thunk

  • 优点: 库小,代码就几行
  • 缺点:代码臃肿,reducer不再是纯粹函数,直接返回对象,违背了当初的设计原则;action的形式不统一,异步操作太为分散,分散在了各个action中

redux-saga

  • 优点: 将异步与reducer区分开了,更加优雅,适合大量APi请求,而且每个请求之间存在复杂的依赖关系
  • 缺点:学习曲线比较陡,理解async await;而且库也比较大,即使发布的最小也有25kb,gzip压缩后也有7KB,React压缩后才45kb

♥︎♥︎♥︎♥︎ React复用组件的状态和增强功能的方法

  • render props模式
  • 高阶组件(HOC)

注意:以上两种方式不是新的API,而是演化而成的一种固定模式(写法)

  • 思路:将要复用的state和操作state的方法封装在一个组件里面(在组件中提供复用的状态逻辑代码,即状态和操作状态的方法)

  • 这时,我们就要思考两个问题:(1)状态是组件内部私有的,那么如何在复用组件的时候拿到组件内部的state呢?—-> 可以在使用组件的时候,添加一个值为函数的props,那么就可以通过函数参数来获取组件内部的state。(2)复用组件时,需要渲染的UI结构会不一样,那么怎么在复用组件时实现渲染任意的UI呢?—–>将函数的返回值作为要渲染的UI

  • 注意:复用的组件并没有渲染任何的UI结构,而且通过函数的返回值来渲染的。

    以下通过一个简单的例子来演示使用render props模式实现的组件复用(一个效果是随着鼠标移动,获取鼠标的位置,另一个效果是图片随着鼠标移动而移动,这两个效果的实现都要获取x和y坐标,所以可以考虑用组件的复用来实现)

Mouse组件(要复用的组件)

import React from "react";
class Mouse extends React.Component {
    //复用的state
    state = {
        x: 0,
        y: 0
    }
    //操作state的方法
    handleMouseMove = e => {
        this.setState({
            x: e.clientX,
            y: e.clientY
        })
    }
    //监听鼠标移动事件
    componentDidMount() {
        window.addEventListener("mousemove", this.handleMouseMove)
    }
    render(){
        return this.props.render(this.state)   //通过函数参数暴露组件内部的状态
    }
}
export default Mouse;

Mouse_Tree组件(复用Mouse组件的组件)

import React from "react";
import Mouse from "./Mouse.js";
import img from "./images/tree.PNG"

class Mouse_Cat extends React.Component {
    render(){
        return(
            <div>
                <Mouse render={ mouse => {
                    return(
                        <p>X坐标为:{mouse.x} Y坐标为:{mouse.y}</p>
                    )
                }}/>  
                <Mouse render={ mouse => {
                    return(
                        <img src={img} alt="树" style={{
                            position: 'absolute',
                            top: mouse.y,
                            left: mouse.x
                        }}/>
                    )
                }}/>    
            </div>
        )
    }
}
export default Mouse_Cat;

♥︎♥︎♥︎♥︎♥︎ React有哪些性能优化的手段?

1、使用纯组件;

2、使用 React.memo 进行组件记忆(React.memo 是一个高阶组件),对于相同的输入,不重复执行;

3、如果是类组件,使用 shouldComponentUpdate(这是在重新渲染组件之前触发的其中一个生命周期事件)生命周期事件,可以利用此事件来决定何时需要重新渲染组件;

4、路由懒加载;

5、使用 React Fragments 避免额外标记;

6、不要使用内联函数定义(如果我们使用内联函数,则每次调用“render”函数时都会创建一个新的函数实例);

7、避免在Willxxx系列的生命周期中进行异步请求,操作dom等;

8、如果是类组件,事件函数在Constructor中绑定bind改变this指向;

9、避免使用内联样式属性;

10、优化 React 中的条件渲染;

11、不要在 render 方法中导出数据;

12、列表渲染的时候加key;

13、在函数组件中使用useCallback和useMemo来进行组件优化,依赖没有变化的话,不重复执行;

14、类组件中使用immutable对象;

♥︎♥︎♥︎♥︎♥︎ 调用this.setState之后,React都做了哪些操作?怎么拿到改变后的值?

如果是在隶属于原生js执行的空间,比如说setTimeout里面,setState是同步的,那么每次执行setState将立即更新this.state,然后触发render方法;因为是同步执行,可以直接获取改变后的值;

如果是在被react处理过的空间执行,比如说合成事件里,此时setState是异步执行的,并不会立即更新this.state的值,当执行setState的时候,会将需要更新的state放入状态队列,在这个空间最后再合并修改this.state,触发render;setState接受第二个参数,是一个回调函数,可以在这里获取改变后的state值;

触发render执行后,会生成一个新的虚拟dom结构,然后触发diff运算,找到变化的地方,重新渲染;

♥︎♥︎♥︎♥︎♥︎ 什么是 React Fiber?

React Fiber En

Fiber是React16中新的协调引擎,它的主要目的是使Virtual DOM可以进行增量式渲染,让界面渲染更流畅一种流程控制原语,也称为协程,可以类比es6中的generator函数;React渲染的过程可以被中断,可以将控制权交回浏览器,让位给高优先级的任务,浏览器空闲后再恢复渲染。

一个执行单元,每次执行完一个“执行单元”,React就会检查现在还剩多少时间,如果没有时间就将控制权让出去。

目标

  • 把可中断的工作拆分成小任务
  • 对正在做的工作调整优先次序、重做、复用上次(做了一半的)成果
  • 在父子任务之间从容切换(yield back and forth),以支持React执行过程中的布局刷新
  • 支持render()返回多个元素
  • 更好地支持error boundary

特性

  • 增量渲染(把渲染任务拆分成块,匀到多帧)
  • 更新时能够暂停,终止,复用渲染任务
  • 给不同类型的更新赋予优先级
  • 并发方面新的基础能力

♥︎♥︎♥︎♥︎♥︎ 简述一下 memoization

memoization最初是用来优化计算机程序使之计算的更快的技术,是通过存储调用函数的结果并且在同样参数传进来的时候返回结果。大部分应该是在递归函数中使用。memoization 是一种优化技术,避免一些不必要的重复计算,可以提高计算速度。

以阶乘函数为例:

1.不使用memoization

const factorial = n => {
  if (n === 1) {
    return 1
  } else {
    return factorial(n - 1) * n
  }
}

2.使用memoization

const cache = [] // 定义一个空的存放缓存的数组
const factorial = n => {
  if (n === 1) {
    return 1
  } else if (cache[n - 1]) { // 先从cache数组里查询结果,如果没找到的话再计算
    return cache[n - 1]
  } else {
    let result = factorial(n - 1) * n
    cache[n - 1] = result
    return result
  }
}

3.搭配闭包使用memoization

const factorialMemo = () => {
  const cache = []
  const factorial = n => {
    if (n === 1) {
      return 1
    } else if (cache[n - 1]) {
      console.log(`get factorial(${n}) from cache...`)
      return cache[n - 1]
    } else {
      let result = factorial(n - 1) * n
      cache[n - 1] = result
      return result
    }
  }
  return factorial
}
const factorial = factorialMemo()

4.总结

memorization 可以把函数每次的返回值存在一个数组或者对象中,在接下来的计算中可以直接读取已经计算过并且返回的数据,不用重复多次相同的计算。是一个空间换时间的方式,这种方法可用于部分递归中以提高递归的效率。

♥︎♥︎♥︎♥︎♥︎ 自定义hooks(重点❗️❗️❗️)

[实现自定义hooks案例]: “https://juejin.cn/post/6844904074433789959

Donate
  • Copyright: Copyright is owned by the author. For commercial reprints, please contact the author for authorization. For non-commercial reprints, please indicate the source.
  • Copyrights © 2022-2023 alan_mf
  • Visitors: | Views:

请我喝杯咖啡吧~

支付宝
微信