react+ts总结

  1. TS 和 JS 的区别是什么?有什么优势?
    语法层面:TypeScript = JavaScript + Type(TS 是 JS 的超集)
    执行环境层面:浏览器、Node.js 可以直接执行 JS,但不能执行 TS(Deno 可以执行 TS)
    编译层面:TS 有编译阶段,JS 没有编译阶段(只有转译阶段「webpack打包 ES6->ES5」和link阶段「ESLink:提示代码写的不规范」)
    编写层面:TS 更难写一些,但是类型更安全
    文档层面:TS 的代码写出来就是文档,IDE 可以完美提示,JS 的提示主要靠 TS
  2. any、unknown、never 的区别是什么?
    any V.S. unknown

两者都是顶级类型(top type),任何类型的值都可以赋值给顶级类型变量(类型值可向上赋值):

let foo:any = 123; // 不报错
let bar:unknown = 123; // 不报错

但是 unknown 比 any 的类型检查更严格,any 什么检查都不做,unknown 要求先收窄类型:

const value:unknown = "Hello World";
const someString:string = value;
// 报错:Type 'unknown' is not assignable to type 'string'.(2322)


const value:unknown = "Hello World";
const someString:string = value as string; // 不报错 使用了类型收窄

如果改成 any,基本在哪都不报错。所以能用 unknown 就优先用 unknown,类型更安全一些

never

never 是底类型,表示不应该出现的类型,这里有一个尤雨溪给出的例子:

interface A {
  type:'a'
}
interface B {
  type:'b'
}
type All = A | B
function handleValue(val:All){
  switch(val.type){
    case 'a':
      // 这里 value 被收窄为 A
      break
    case 'b':
      // val 在这里是 B
      break
    default:
      // val 在这里是 never
      const exhaustiveCheck: never = val
      break
  }
}
  1. type 和 interface 的区别是什么?
    组合方式:interface 使用 extends 来实现继承,type 使用 & 来实现联合类型
    扩展方式:interface 可以重复声明用来扩展,type 一个类型只能声明一次
    范围不同:type 适用于基本类型,interface 一般不行
    命名方式:interface 会创建新的类型名,type 只是创建类型别名,并没有新创建类型
  2. TS 工具类型 Partial、Required、Readonly、Exclude、Extract、Omit、ReturnType 的作用和实现?
    Partial 部分类型
    interface User{
    id:string;
    name:string
    }
    const user:Partial{ // id 可不写
    name:'Evelyn'
    }
    Required 必填类型
    interface User{
    id?:string;
    name:string;
    }
    const user:Required = { // id为必填
    id:'111';
    name:'Evelyn'
    }
    Readonly 只读类型
    interface User{
    id?:string;
    name:string;
    }
    const user:Readonly = { // id为必填
    id:'111';
    name:'Evelyn'
    }
    user.id = '222' //报错 Readonly 只读类型
    Exclude 排除类型:后接基本类型
    type Dir = '东'|'南'|'西'|'北'
    type Dir2 = Exclude //排除 '北'
    Extract 提取类型
    type Dir = '东'|'南'|'西'|'北'
    type Dir3 = Extract //只要 '东' '西'
    Pick / Omit 排除 key 类型:Omit 后接对象类型
    interface User{
    id:string;
    name:string;
    age:number
    }
    type God = Pick // 只要 id name
    type God1 = Omit //只是不要 age 属性
    ReturnType 返回值类型
    function f(a:number,b:number){
    return 'a+b'
    }
    type A = ReturnType
    注:Map 是不限制类型的 Record,Record 是限制类型的 Map

1. 虚拟 DOM 的原理是什么?

(1)是什么?虚拟 DOM 就是虚拟节点,React 用 JS 对象来模拟 DOM 节点,然后将其渲染成真实的 DOM 节点

(2)怎么做?

第一步是模拟:

用JSX语法写出来的 div 其实就是一个虚拟节点

hi

这代码会得到这样一个对象:用一个对象模拟节点

对象有三个属性:

  • tag:表示什么标签
  • props:表示标签上有哪些属性
  • children:表示有哪些子标签 / 子文本
{
  tag:'div', // 表示什么标签
  props:{  // 表示标签上有哪些属性
    id:'x'
  },
  children:[  // 表示有哪些子标签/子文本
    {
      tag:'span',
      props:{
        className:'red'
      }
     children:[
       'hi'
     ]
    }
  ]
}

能做到这一点是因为 JSX 语法会被转译为 createElement 函数调用(也叫 h 函数),通过 Babel 或 Webpack 的loader来实现的如下:

React.createElement("div",{id:"x"},
  React.createElement("span",{className:"red"},"hi")  
)

第二步是将虚拟节点渲染为真实节点

function render(vdom){
  // 如果是字符串或者数字,创建一个文本节点
  if(typeof vdom === 'string' || typeof vdom === 'number'){
    return document.createTextNode(vdom)
  }
  const {tag,props,children} = vdom
  // 创建真实 DOM(标签)
  const element = document.createElement(tag)
  // 设置属性
  setProps(element,props)
  // 遍历子节点,并获取创建真实 DOM,插入到当前节点
  children.map(render)
          .forEach(element.appendChild.bind(element))
  // 虚拟 DOM 中缓存真实 DOM 节点,虚拟的和真实的建立关联
  vdom.dom = element

  // 返回 DOM 节点
  return element
}

function setProps
function setProp

注意:如果节点发生变化,并不会直接把新虚拟节点渲染到真实节点,而是先经过 diff 算法得到一个 patch 再更新到真实节点上

(3)解决了什么问题

  • DOM 操作性能问题,通过虚拟 DOM 和 diff 算法减少不必要的 DOM 操作,保证性能不太差
  • DOM 操作不方便问题,以前各种 DOM API 要记,现在只有 setState

(4)优点

  • 为 React 带来了跨平台能力,因为虚拟节点除了渲染为真实节点,还可以渲染为其他东西
  • 让 DOM 操作的整体性能更好,能(通过 diff)减少不必要的 DOM 操作

(5)缺点

①性能要求极高的地方,还是得用真实 DOM 操作

②React 为虚拟 DOM 创造了合成事件,跟原生 DOM 事件不太一样,工作中要额外注意

  • 所有 React 事件都绑定到根元素,自动实现事件委托
  • 如果混用合成时间和原声 DOM 事件,有可能会出 bug

2. React 或 Vue 的 DOM diff 算法是怎样的?

(1)是什么

DOM diff 就是对比两颗虚拟 DOM 树的算法,当组件变化时,会 render 出一个新的虚拟 DOM,diff 算法对比新旧虚拟 DOM 之后,得到一个 patch,然后 React 用 patch 来更新真实的 DOM

(2)怎么做

首先,对比两颗树的根节点

①如果根节点的类型改变了,比如 div 变成了 p,那么直接认为整棵树都变了,不再对比子节点。此时直接删除对应的真实 DOM 树,创建新的真实 DOM 树

②如果根节点的类型没变,就看看属性变了没有

a. 如果没变,就保留对应的真实节点

b. 如果变了,就只更新该节点的属性,不重新创建节点

i. 更新 style 时,如果多个 CSS 属性只有一个改变了,那么 React 只更新改变的

然后同时遍历两棵树的子节点,每个节点的对比过程同上,不过存在如下两种情况:

①情况一

  • A
  • B
  • A
  • B
  • C

React 一次对比 A-A、B-B、空-C,发现 C 是新增的,最终会创建真实 C 节点插入页面

②情况二

  • B
  • C
  • A
  • B
  • C

React 对比 B-A,会删除 B 文本新建 A 文本;对比 C-B,会删除 C 文本,新建 B 文本;(注意:并不是边对比边删除新建,而是把操作汇总到 patch 里再进行 DOM 操作)对比 空-C,会新建 C 文本

由此发现其实只需要创建 A 文本,保留 B 和 C 即可,为什么 React 做不到呢?

因为 React 需要加 key 才能做到:

  • B
  • C
  • A
  • B
  • C

React 先对比 key 发现 key 只新增了一个,于是保留 b 和 c,新建 a

以上是 React 的 diff 算法,Vue 的 diff 算法是「双端交叉对比」算法

假设有旧的 Vnode 数组和新的 Vnode数组这两个数组,有四个变量充当指针分别指向两个数组的头尾,重复下面的对比过程,知道两个数组中任一数组的头指针超过尾指针,循环结束

  • 头头对比:对比两个数组的头部,如果找到,把新节点 patch 到旧节点,头指针后移
  • 尾尾对比:对比两个数组的尾部,如果找到,把新节点 patch 到旧节点,尾指针前移
  • 旧尾新头对比:交叉对比,旧尾新头,如果找到,把新节点 patch 到旧节点,旧尾针前移,新头指针后移
  • 旧头新尾对比:交叉对比,旧头新尾,如果找到,把新节点 patch 到旧节点,新尾指针前移,旧头指针后移
  • 利用 key 对比:用新指针对应节点的 key 去旧数组寻找对应的节点,这里分三种情况,当没有对应的 key,那么创建新的节点,如果有 key 并且是相同的节点,把新节点 patch 到旧节点,如果有 key 但是不是相同的节点,则创建新节点

循环结束后,两个数组中可能存在未遍历完的情况,所以循环结束后:

  • 先对比旧数组的头尾指针,如果旧数组遍历完了(可能新数组没遍历完,有漏添加的问题),添加新数组中漏掉的节点
  • 再对比新数组的头尾指针,如果新数组遍历完了(可能旧数组没遍历完,有漏删除的问题),删除旧数组中漏掉的节点

3. React DOM diff 和 Vue DOM diff 的区别?

① React 是从左向右遍历对比,Vue 是双端交叉对比

② React 至少需要维护三个变量(lastPlacedIndex、newIdx、nextOldFiber),Vue 则至少需要维护四个变量

③ Vue整体效率比 React 更高,举例说明:假设有 N 个子节点,我们只是把最后子节点移到第一个,那么

  • React 需要进行借助 Map 进行 key 搜索找到匹配项,然后复用节点
  • Vue 会发现移动,直接复用节点

4. React 有哪些生命周期钩子函数?数据请求放在哪个钩子里?

Mounting(挂载时):

  • constructor() -- 类的构造函数
  • static getDerivedStateFromProps()
  • render()
  • componentDidMount()

Updating(更新时):

  • static getDerivedStateFromProps()
  • shouldComponentUpdate()
  • render()
  • getSnapshotBeforeUpdate()
  • componentDidUpdate()

Unmounting(卸载时):

  • componentWillUnmount()

Error Handing(错误处理):

  • static getDerivedStateFromError()
  • componentDidCatch()

默认是 class 组件, hooks 函数组件是没有生命周期的

总的来说:

  • 挂载时调用 constructor,更新时不调用
  • 更新时调用shouldComponentUpdate 和 getSnapshotBeforeUpdate,挂载时不调用
  • shouldComponentUpdate 在 render 之前调用,getSnapshotBeforeUpdate 在 render 后调用
  • 请求放在 componentDidMount 里

请求不能放在 constructor 中因为它在 SSR 服务器端被调用,拿不到数据,更新阶段的 5 个生命周期会在每一次更新的时候都会调用,可能会触发多余的或无限调用,卸载时发送请求显然没什么意义,故数据请求只能放在 componentDidMount

5. React 如何实现组件间通信

(1)父子间通信:props + 函数(父亲给儿子传数据直接传 props,儿子要给父亲传东西就调用父亲传给他的函数)

(2)爷孙组件通信:两层父子通信或者使用 Context.Provider 和 Context.Consumer

(3)任意组件通信:其实就变成了状态管理了,那数据放到集中的地方

  • Redux
  • Mobx
  • Recoil :局部

6. 你如何理解 Redux?

Redux 是一个状态管理库 / 状态容器

核心概念有:

  • state:用来存放状态
  • Action:对数据的改变 Action = type + payload荷载
  • Reducer:是一个函数,传一个旧的 state ,(state 和 Action)产生一个新的 state
  • Dispatch:后面接 Action,用来派发
  • Middleware

Redux 和 ReactRedux 配合使用,关于 ReactRedux 有三个核心概念:

  • connect:接受两次参数 connect()(Component) 其作用是把 Component 和 store 关联起来
  • mapStateToProps
  • mapDispatchToProps

常见的中间件:redux-thunk redux-promise

后续补充..........

7. 什么是高阶组件 HOC?

参数是组件,返回值也是组件的函数

  • React.forwardRef

函数组件中不支持 ref,没办法去生命一个 ref 去引用button,函数组件没有生命周期,所以没办法持久化的保存对 button 的引用

function FancyButton(props) {
  return (
    
  );
}

FancyButton使用React.forwardRef来获取传递给它的ref,然后转发到它渲染的 DOMbutton

函数组件外面使用 forwardRef(把组件传给函数)

const FancyButton = React.forwardRef((props, ref) => (
  
));

// 你可以直接获取 DOM button 的 ref:
const ref = React.createRef();
Click me!;

  • ReactRedux 的 connect
  • ReactRouter 的 withRouter

8. React Hooks 如何模拟组件生命周期?

  • 模拟 componentDidMount:使用 useEffect 时,第二个参数为空数组
  • 模拟 componentDidUpdate:使用 useEffect 时,不加第二个参数,或者是在第二个参数的数组里加上要监听的变量
  • 模拟 componentWillUnmount:在 did mount 后面写 return 函数

代码示例如下:代码运行

import { useEffect, useRef, useState } from "react";
import "./styles.css";

export default function App() {
  const [visible, setNextVisible] = useState(true);
  const onClick = () => {
    setNextVisible(!visible);
  };
  return (
    

Hello CodeSandbox

{visible ? : null}
); } function Evelyn(props) { const [n, setNextN] = useState(0); const first = useRef(true); // 模拟 componentDidUpdate,第二个参数为要监听的数据,监听 n 第二个参数是[n] // 若想监听所有数据,直接第二个参数为空即可 useEffect(() => { if (first.current === true) { return; } console.log("did update"); //挂载的时候不调用 }); // 模拟 componentDidMount,第二个参数为空数组 useEffect(() => { console.log("did mount"); first.current = false; //第一次调用为挂载,其值置为 false // 模拟 componentWillUnmount,return 为当前组件消失的时候执行 return () => { console.log("did unmount"); }; }, []); const onClick = () => { setNextN(n + 1); }; return (
Evelyn
); }

你可能感兴趣的:(react+ts总结)