核心概念还是没变

一些高级指引要需要重新学了

组件的按需加载Suspense&Lazy

在 Suspense 组件中渲染 lazy 组件,如此使得我们可以使用在等待加载 lazy 组件时做优雅降级(如 loading 指示器等)

React.lazy 接受一个函数,这个函数需要动态调用 import()。它必须返回一个 Promise,该 Promise 需要 resolve 一个 default export 的 React 组件。

  1. # before
  2. import OtherComponent from './OtherComponent';
  3. # after
  4. const OtherComponent = React.lazy(() => import('./OtherComponent'));
  5. # demo:局部按需加载
  6. import React, { Suspense } from 'react';
  7. import MyErrorBoundary from './MyErrorBoundary';
  8. const OtherComponent = React.lazy(() => import('./OtherComponent'));
  9. function MyComponent() {
  10. return (
  11. <div>
  12. <MyErrorBoundary> # 异常捕获组件
  13. # Suspense包裹按需加载的组件,fallback用于优雅降级
  14. <Suspense fallback={<div>Loading...</div>}>
  15. <section>
  16. <OtherComponent />
  17. <AnotherComponent />
  18. </section>
  19. </Suspense>
  20. </MyErrorBoundary>
  21. </div>
  22. );
  23. }
  24. # 基于路由的按需加载
  25. import React, { Suspense, lazy } from 'react';
  26. import { BrowserRouter as Router, Route, Switch } from 'react-router-dom';
  27. const Home = lazy(() => import('./routes/Home'));
  28. const About = lazy(() => import('./routes/About'));
  29. const App = () => (
  30. <Router>
  31. <Suspense fallback={<div>Loading...</div>}>
  32. <Switch>
  33. <Route exact path="/" component={Home}/>
  34. <Route path="/about" component={About}/>
  35. </Switch>
  36. </Suspense>
  37. </Router>
  38. )

Context

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

什么样的数据适合放在 context树上?
如:地区偏好,UI 主题、当前认证的用户、首选语言

如何使用从上到下传递值

  1. ===1.全局变量创建===
  2. 创建全局变量:#theme-context.js
  3. export const themes = {
  4. light: {
  5. background: '#eeeeee',
  6. },
  7. dark: {
  8. background: '#222222'
  9. },
  10. };
  11. # React.createContext创建全局变量
  12. export const ThemeContext = React.createContext(
  13. themes.dark // 默认值
  14. );
  15. ===2.变量消费者 最细节的组件===
  16. #themed-button.js 使用全局变量的 最细枝末节的组件
  17. import {ThemeContext} from './theme-context';
  18. class ThemedButton extends React.Component {
  19. render() {
  20. let props = this.props;
  21. let theme = this.context;
  22. return (
  23. <button
  24. {...props}
  25. style={{backgroundColor: theme.background}}
  26. />
  27. );
  28. }
  29. }
  30. # 将全局变量挂载在 组件的contextType属性,这样组件内部可以通过this.context读到全局变量
  31. ThemedButton.contextType = ThemeContext;
  32. export default ThemedButton;
  33. ===3.变量消费者 的宿主 需要给末端组件提供宿主环境,即用provider包裹住变量使用者===
  34. #app.js
  35. class App extends React.Component {
  36. render() {
  37. return (
  38. <Page>
  39. <ThemeContext.Provider value={this.state.theme}>
  40. <ThemedButton onClick={props.changeTheme}>
  41. Change Theme
  42. </ThemedButton>
  43. </ThemeContext.Provider>
  44. <Section>
  45. <ThemedButton />
  46. </Section>
  47. </Page>
  48. );
  49. }

什么时候使用?
可以滥用吗?==> 请谨慎使用,因为这会使得组件的复用性变差。
其他方案:
如果你只是想避免层层传递一些属性,试试组件组合(component composition)

当 Provider 的 value 值发生变化时,它内部的所有消费组件都会重新渲染
Provider 及其内部 consumer 组件都不受制于 shouldComponentUpdate 函数

在很深的组件中更新context

解决方案:在变量的那一层,就定义修改变量的函数,并把这个函数传参给下面的用

HOOK

可以让我们在不编写class的情况下使用state及其他的React特性。使你在无需修改组件结构的情况下复用状态逻辑。

为什么要用?

提问:既然要使用state了,为啥还把组件设计为function组件 而不是class组件?
那我们思考一下 class组件有什么 不好的地方?
1. class组件状态管理逻辑复杂
React组件不能直接连接到store,在组件嵌套层级较深或者同级组件较多时,虽然可以使用render props、HOC等方案。但是组件间复用状态的处理比较困难。
所以相对来说复杂一点的项目就需要引用Redux或者Mobx做状态管理。
Hook则为React 提供了共享状态逻辑更好的原生途径。

2. class组件在生命周期里做状态逻辑处理容易导致副作用使得难以维护
例1:在componentDidMount或者componentDidUpdate中调用接口,获取数据,
会涉及鉴权、数据format等造成state频繁修改,页面有多次不必要的render。

例2:是生命周期使用了原生事件时。比如在componentDidMount设置了addEventListener,不会在组件卸载(unmount)的时候自动销毁,需要手动remove,增加了维护成本

Hook 并非强制按照生命周期划分,它可以使我们将组件中相互关联的部分拆分成更小的函数(比如设置订阅或请求数据),这样组件间功能更简洁,我们就使用更少的代码实现相同的功能,逻辑看起来也更清晰。

使用 Hook 其中一个目的就是要解决 class 中生命周期函数经常包含不相关的逻辑,但又把相关逻辑分离到了几个不同方法中的问题。
Hook 允许我们按照代码的用途分离他们,** 而不是像生命周期函数那样。React 将按照 effect 声明的顺序依次调用组件中的每一个 effect。

3. class组件难以理解如this绑定
比如class中this的绑定
还有class组件预编译、压缩、热重载不稳定等等其他问题。。。

综上class组件的弊端,所以我们在引入hook后,可以在使用function组件的同时又可以使用更多的 React 特性。

如何使用:api们

Hook 就是 JavaScript 函数,使用限制:

  • 只能在函数最外层调用 Hook。不要在循环、条件判断或者子函数中调用。
  • 只能在 React 的函数组件中调用 Hook。不要在其他 JavaScript 函数中调用。
  • 自定义HOOK里调用

useState

可以充当constructor

  1. # [变量,改变变量的方法] = useState(变量的初始值)
  2. const [value, setValue] = useState(defaultValue)
  3. setValue也只是接受一个新的值,直接覆盖原来的value

useState(唯一)参数是初始state,可以是数字、字符串或者object。
返回值是一个二项数组:当前状态value;一个更新state的函数setValue,它类似于class的this.setState,可以在合成或原生事件等其他地方调用这个函数。
useState给组件添加一些内部state,React会在重复render时保留这个state当前的value,并且提供最新的value给函数setValue。
可以在一个组件中多次使用。
当你多次调用 useState 的时候,React 将按照 effect 声明的顺序依次调用组件中的每一个 effect。


  1. # demo
  2. import React, { useState } from 'react';
  3. function Example() {
  4. // 声明一个叫 “count” 的 state 变量。
  5. const [count, setCount] = useState(0);
  6. return (
  7. <div>
  8. <p>You clicked {count} times</p>
  9. <button onClick={() => setCount(count + 1)}>
  10. Click me
  11. </button>
  12. </div>
  13. );
  14. }

useEffect

  1. # api
  2. useEffect(() => {
  3. // do sth
  4. return (() => {}); // 通过return一个函数指定如何清除副作用
  5. }, [dependencies]); # 配置项用于改变effect执行的时机
  6. dependencies:
  7. 1.为空:效果不依赖任何东西,并且在第一个渲染之后仅运行一次
  8. 2.有值数组:效果将被限制为仅在这些依赖项(useState里的变量)更改时才运行;
  9. 3.省略,则效果将在每次render后运行;

什么是副作用?
=> 在 React 组件中执行数据获取、订阅或者手动修改过 DOM
useEffect是Effect Hook中的一种,给函数组件增加了操作副作用的能力。
对应:componentDidMount, componentDidUpdate, componentWillUnmount,相当于合并成了一个 API。
用途:执行数据获取、订阅或者修改DOM这些有副作用的操作。

如,下面这个组件在 React 更新 DOM 后会设置一个页面标题:

调用 useEffect 就是在告诉 React 在完成对 DOM 的更改后运行你的“副作用”函数 补充:React 会在上一轮的render完成后(且浏览器绘制后),新的render执行前调用useEffect,执行effect —— 包括第一次渲染和每次更新

是不是相当于 vue的nextTick 插入到了当前eventloop的dom修改后(还是有点不一样,nextTick是绘制前但dom改变后)

  1. # demo
  2. import React, { useState, useEffect } from 'react';
  3. function Example() {
  4. const [count, setCount] = useState(0);
  5. # useEffect 相当于 componentDidMount componentDidUpdate:
  6. useEffect(() => {
  7. // 使用浏览器的 API 更新页面标题
  8. document.title = `You clicked ${count} times`;
  9. });
  10. return (
  11. <div>
  12. <p>You clicked {count} times</p>
  13. <button onClick={() => setCount(count + 1)}>
  14. Click me
  15. </button>
  16. </div>
  17. );
  18. }

这么一想,好像以前使用function组件的时候,只是木偶组件,如果有对用户操作做反应的handleCilck也只是做了一个调用props.click的方法。

如果用了hook,就可以在木偶组件里使用state和方法了,当然这只是浅显理解
由于副作用函数是在组件内声明的,所以它们可以访问到组件的 props 和 state;
默认情况下,React 会在每次渲染后调用副作用函数 —— 包括第一次渲染的时候(这么一看不就更像vue的nexttick了吗。。)

当副作用是一个监听器的时候,我们怎么手动清除副作用呢
==> 返回一个函数指定如何清除

  1. useEffect(() => {
  2. // 订阅变化
  3. ChatAPI.subscribeToFriendStatus(props.friend.id, handleStatusChange);
  4. // React 会在组件销毁时取消对 ChatAPI 的订阅,然后在后续渲染时重新执行副作用函数
  5. return () => {
  6. ChatAPI.unsubscribeFromFriendStatus(props.friend.id, handleStatusChange);
  7. };
  8. });

使用hook的useEffect,是不是相当于不用纠结到底是在哪个生命周期里去写那些改变state的副作用函数了?
直接useEffect即可。

无需清除的副作用:

只想在 React 更新 DOM 之后运行一些额外的代码。比如发送网络请求,手动变更 DOM,记录日志

需要清除的:及时清除的重要性体现在 防止引起内存泄露

订阅外部数据源。

effect 的清除阶段在每次重新渲染时都会执行,而不是只在卸载组件的时候执行一次
==> 这个设计可以帮助我们创建 bug 更少的组件。

effect 中获取的 state 的值永远是最新的:

React会保存useEffect中的传参函数effect,当调用 useEffect 时,就是在告诉 React 在完成对 DOM 的更改,执行 DOM 更新后运行那个“副作用”函数effect。所以,在 effect 中获取的 state 的值永远是最新的(当前dom上显示的),而不用担心其过期的原因。每次我们重新渲染,也会生成新的 effect,替换掉之前的。

好处:
与 componentDidMount、componentDidUpdate 不同,使用 useEffect 调度的 effect 不会阻塞浏览器更新,这让你的应用看起来响应更快。
大多数情况下,effect 不需要同步地执行。在个别情况下(例如测量布局),可以用useLayoutEffect Hook,其 API 与 useEffect 相同。

useContext

useContext 让你不使用组件嵌套就可以订阅 React 的 Context。

使用规则限制

Hook 就是 JavaScript 函数,使用限制:

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

react是依靠hook调用顺序来区分各个state的,所以不能产生不稳定的条件分支导致调用顺序不可控
如果我们想要有条件地执行一个 effect,可以将判断放到 Hook 的内部

  1. 只能在 React 的函数组件中调用 Hook。不要在其他 JavaScript 函数中调用。
  2. 自定义HOOK里调用

使用hook重构应用

文章链接:[译]React Hooks 实践:如何使用Hooks重构你的应用

没有state或生命周期的组件转换

就将函数代替class即可,this被取代,通过函数作用域来访问变量
image.pngimage.png

带有props并有默认值

image.png image.png

带有state

使用hook
image.png

image.png
更多例子这里

好的实践

使用对象聚合useState

  1. # bad
  2. const [searchKey,setSearchKey] = useState('');
  3. const [current,setCurrent] = useState(1)
  4. const [pageSize,setPageSize] = useState(10)
  5. # good
  6. const [searchParams,setSearchParams] = useState({
  7. searchKey: '',
  8. current:1,
  9. pageSize:10
  10. })

TS学习

快速入门文档:1.2W字 | 了不起的 TypeScript 入门教程

类型

  1. # 数组 Array
  2. let list: number[] = [1, 2, 3];
  3. // ES5:var list = [1,2,3];
  4. let list: Array<number> = [1, 2, 3]; // Array<number>泛型语法
  5. // ES5:var list = [1,2,3];
  6. # 数组里有多种类型的 如[1, 'hello world', 22]
  7. # Tuple元组
  8. let tupleTypeTest: [string, boolean];
  9. tupleTypeTest = ["Semlinker", true];
  10. # 常量枚举enum
  11. enum Direction {
  12. NORTH = "NORTH",
  13. SOUTH = "SOUTH",
  14. EAST = "EAST",
  15. WEST = "WEST",
  16. }
  17. # void类型
  18. 值只能为 undefined null:用于函数没有返回值 就表示 return void类型
  19. function warnUser(): void {
  20. console.log("This is my warning message");
  21. }

any、unknown类型是兜底类型

尖括号orAs语法-断言

通过类型断言这种方式可以告诉编译器,“相信我,我知道自己在干什么”。类型断言好比其他语言里的类型转换,但是不进行特殊的数据检查和解构

  1. let someValue: any = "this is a string";
  2. let strLength: number = (<string>someValue).length;
  3. or
  4. let strLength: number = (someValue as string).length;

函数

  1. # 参数类型及返回类型
  2. function createUserId(name: string, id: number): string {
  3. return name + id;
  4. }
  5. # 可选及默认参数
  6. function createUserId(
  7. name: string = "Semlinker",
  8. id: number,
  9. age?: number #可选
  10. ): string {
  11. return name + id;
  12. }

类与接口

interface
**

  1. interface Person {
  2. name: string;
  3. age: number;
  4. }
  5. let Semlinker: Person = {
  6. name: "Semlinker",
  7. age: 33,
  8. };

静态属性与方法:(相当于直接挂在类上,成员方法是挂在原型上的)

  1. class Greeter {
  2. // 静态属性
  3. static cname: string = "Greeter";
  4. // 成员属性
  5. greeting: string;
  6. // 构造函数 - 执行初始化操作
  7. constructor(message: string) {
  8. this.greeting = message;
  9. }
  10. // 静态方法
  11. static getClassName() {
  12. return "Class name is Greeter";
  13. }
  14. // 成员方法
  15. greet() {
  16. return "Hello, " + this.greeting;
  17. }
  18. }
  19. let greeter = new Greeter("world");

私有变量

  1. let passcode = "Hello TypeScript";
  2. class Employee {
  3. private _fullName: string;
  4. get fullName(): string {
  5. return this._fullName;
  6. }
  7. set fullName(newName: string) {
  8. if (passcode && passcode == "Hello TypeScript") {
  9. this._fullName = newName;
  10. } else {
  11. console.log("Error: Unauthorized update of employee!");
  12. }
  13. }
  14. }
  15. let employee = new Employee();
  16. employee.fullName = "Semlinker";
  17. if (employee.fullName) {
  18. console.log(employee.fullName);
  19. }

泛型

设计泛型的关键目的是在成员之间提供有意义的约束,这些成员可以是:类的实例成员、类的方法、函数参数和函数返回值。 泛型(Generics)是允许同一个函数接受不同类型参数的一种模板。相比于使用 any 类型,使用泛型来创建可复用的组件要更好,因为泛型会保留参数类型。

  • T(Type):表示一个 TypeScript 类型
  • K(Key):表示对象中的键类型
  • V(Value):表示对象中的值类型
  • E(Element):表示元素类型
  1. class GenericNumber<T> {
  2. zeroValue: T;
  3. add: (x: T, y: T) => T;
  4. }
  5. #通过 extends 关键字添加泛型约束
  6. interface ILengthwise {
  7. length: number;
  8. }
  9. function loggingIdentity<T extends ILengthwise>(arg: T): T {
  10. console.log(arg.length);
  11. return arg;
  12. }
  13. #Partial<T> 的作用就是将某个类型里的属性全部变为可选项 ?。
  14. type Partial<T> = {
  15. [P in keyof T]?: T[P];
  16. };
  17. 通过 keyof T 拿到 T 的所有属性名,然后使用 in 进行遍历,将值赋给 P
  18. 最后通过 T[P] 取得相应的属性值。中间的 ? 号,用于将所有属性变为可选
  19. interface Todo {
  20. title: string;
  21. description: string;
  22. }
  23. const todo1 = {
  24. title: "organize desk",
  25. description: "clear clutter",
  26. };
  27. function updateTodo(todo: Todo, fieldsToUpdate: Partial<Todo>) {
  28. return { ...todo, ...fieldsToUpdate };
  29. }
  30. const todo2 = updateTodo(todo1, {
  31. description: "throw out trash",
  32. });
  33. # todo2 是基于todo1,但是属性都是可选的了
  34. =>
  35. {
  36. title?: string | undefined;
  37. description?: string | undefined;
  38. }

装饰器

好的demo

ts代码整洁之道

不推荐函数参数超过3个

  1. # bad
  2. function getList(
  3. searchName:string,
  4. pageNum:number,
  5. pageSize:number,
  6. key1:string,
  7. key2:string
  8. ){
  9. ...
  10. }
  11. # good: 超过3个 使用对象来聚合。
  12. interface ISearchParams{
  13. searchName:string;
  14. pageNum:number;
  15. pageSize:number;
  16. key1:string;
  17. key2:string;
  18. }
  19. function getList(params:ISearchParams){
  20. }