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<User>{ // id 可不写
    name:'Evelyn'
    }
    Required 必填类型
    interface User{
    id?:string;
    name:string;
    }
    const user:Required<User> = { // id为必填
    id:'111';
    name:'Evelyn'
    }
    Readonly 只读类型
    interface User{
    id?:string;
    name:string;
    }
    const user:Readonly<User> = { // id为必填
    id:'111';
    name:'Evelyn'
    }
    user.id = '222' //报错 Readonly 只读类型
    Exclude 排除类型:后接基本类型
    type Dir = '东'|'南'|'西'|'北'
    type Dir2 = Exclude<Dir,'北'> //排除 '北'
    Extract 提取类型
    type Dir = '东'|'南'|'西'|'北'
    type Dir3 = Extract<Dir,'东'|'西'> //只要 '东' '西'
    Pick / Omit 排除 key 类型:Omit 后接对象类型
    interface User{
    id:string;
    name:string;
    age:number
    }
    type God = Pick<User,'id'|'name'> // 只要 id name
    type God1 = Omit<User,'age'> //只是不要 age 属性
    ReturnType 返回值类型
    function f(a:number,b:number){
    return 'a+b'
    }
    type A = ReturnType<typeof f>
    注:Map 是不限制类型的 Record,Record 是限制类型的 Map

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

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

(2)怎么做?

第一步是模拟:

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

<div id="x">
  <span class="red">hi</span>
</div>

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

对象有三个属性:

  • 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 只更新改变的

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

①情况一

<ul>
  <li>A</li>
  <li>B</li>
</ul>

<ul>
  <li>A</li>
  <li>B</li>
  <li>C</li>
</ul>

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

②情况二

<ul>
  <li>B</li>
  <li>C</li>
</ul>

<ul>
  <li>A</li>
  <li>B</li>
  <li>C</li>
</ul>

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

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

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

<ul>
  <li key="b">B</li>
  <li key="c">C</li>
</ul>

<ul>
  <li key="a">A</li>
  <li key="b">B</li>
  <li key="c">C</li>
</ul>

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 (
    <button className="FancyButton">
      {props.children}
    </button>
  );
}

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

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

const FancyButton = React.forwardRef((props, ref) => (
  <button ref={ref} className="FancyButton">
    {props.children}
  </button>
));

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

  • 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 (
    <div className="App">
      <h1>Hello CodeSandbox</h1>
      {visible ? <Evelyn /> : null}
      <div>
        <button onClick={onClick}>toggle</button>
      </div>
    </div>
  );
}
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 (
    <div>
      Evelyn
      <button onClick={onClick}>+1</button>
    </div>
  );
}
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 214,504评论 6 496
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,434评论 3 389
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 160,089评论 0 349
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,378评论 1 288
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,472评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,506评论 1 292
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,519评论 3 413
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,292评论 0 270
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,738评论 1 307
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,022评论 2 329
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,194评论 1 342
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,873评论 5 338
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,536评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,162评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,413评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,075评论 2 365
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,080评论 2 352

推荐阅读更多精彩内容