TS入门014ts高级类型下

可识别联合

  • a
  • TS可以通过一个特征值来区分类型
    • 在 Redux 的 Action 中经常用到

我们需要 N 选一

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
interface Props {
action:'create'|'update';
id?:number;
name:string
}

// 创建的时候
{
action:'create',
// 创建时没id
name:'aaa'
}

// 更新的时候
{
action:'update',
// 更新时必须有id
id:1,
name:'bbb'
}

// 此时你可以这样

type Props2 = {
action:'create';
name:string;
} | {
action:'update';
id:number;
name:string;
}

function fn(a:Props2){
if(a.action === 'create'){
console.log(a.name)
}else{
console.log(a.id)
}
}

可识别联合的前置条件

  • 必须有一个或多个共有字段 如上面的 action
  • 共有类型必须是可以穷举的

Redux 的 Actoin

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
type Action = {
type:'add',payload:number;
} | {
type:'delete',payload:string;
} | {
type:'update',payload:Date;
}


function reducer(state:any,actoin:Action){
switch(actoin.type){
case 'add':
console.log(actoin.payload) // number
break
case 'delete':
console.log(actoin.payload) // string
break
case 'update':
console.log(actoin.payload) // Date
break
}
}

参考代码 ts014

TS入门013ts高级类型上

类型的且运算

  • 你的新类型 c 必须同时满足 A 和 B接口的约束,否则报错
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
interface A {
name:string;
age:number
}

interface B {
name:string;
grade:number
}

const c: A & B = {
name:'aaa',
age:11,
grade:100
}

React 里使用这个 且运算

1
2
3
4
5
6
7
8
9
10
11
12
13
import React from 'react';

const Layout : React.FunctionComponent & { Header :React.FunctionComponent } = ()=>{
return (
React.createElement('div',null,'hi')
)
}

Layout.Header = ()=>{
return (
React.createElement('div',null,'hi')
)
}

垂直写法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

interface Layout2 extends React.FunctionComponent{
Header:React.FunctionComponent
}

const Layout2:Layout2 = ()=>{
return (
React.createElement('div',null,'hi')
)
}
Layout2.Header = ()=>{
return (
React.createElement('div',null,'hi')
)
}

小知识点:接口名和变量能一样吗?

  • 可以 ts 自己就能识别

类型的或运算

  • 都满足
  • 满足其中一个
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
{
interface A {
name:string;
age:number
}

interface B {
name:string;
grade:number
}

// 都满足
const c: A | B = {
name:'aaa',
age:11,
grade:100
}

// 满足单个
const c1: A | B = {
name:'aaa',
age:11
}
// 满足单个
const c2: A | B = {
name:'aaa',
grade:11
}
}

或运算的歧义

1
2
3
4
5
6
7
8
9
function add(a : number|string,b : number|string){
// 标红
// 运算符“+”不能应用于类型“string | number”和“string | number”。
return a + b;
}

add(1,2)
add('1','2')
add(1,'2')

你想到的解决办法 泛型 其实也不行,因为你怎么保证 这俩类型可以”+”呢?

1
2
3
4
function add<T>(a : T,b : T ){
//运算符“+”不能应用于类型“T”和“T”
return a + b;
}

答案是使用 重载

1
2
3
4
5
6
7
function add(a:string,b : string ):string;
function add(a:number,b : number ):number;
function add(a:any,b : any ):any{
return a + b;
}
add(1,2)
add('1','2')

类型别名 type

  • 给一个已知的类型取一个别名

别名 type 和 interface的区别

  • interface 是声明了一个新的类型
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
import React from 'react';

const Layout : React.FunctionComponent & { Header :React.FunctionComponent } = ()=>{
return (
React.createElement('div',null,'hi')
)
}

Layout.Header = ()=>{
return (
React.createElement('div',null,'hi')
)
}

// 上面的 & 运算之后 类型会显得很长
// 别名的使用:给已知类型一个名字
type MyLayout = React.FunctionComponent & { Header :React.FunctionComponent }
const Layout2 : MyLayout = ()=>{
return (
React.createElement('div',null,'hi')
)
}

Layout2.Header = ()=>{
return (
React.createElement('div',null,'hi')
)
}

字面量类型

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
interface People {
gender : '男' | '女'
}

const a:People = {
// 报错 ,限定了 gender 必须是 '男' or '女'
// gender :'人妖'
gender:'男'
}

// 方向
type Dir = '上' | '下' | '左' | '右'
// const dir: Dir = 'e'; // 直接报错
const dir : Dir = '上'

// 字面量类型 还能包含多个类型取值
type Aa= 1 | 2 | 3 | 4 | 5 | 6
type Bb = true | false | 6 | '上'

// 丰富的类型搭配
interface People {
gender : '男' | '女',
name? : string;
name2: string | undefined;
name3: string | undefined | null;
}

this 也有类型

  • 链式操作
  • this多态
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
{
class Calc {
public value: number
constructor(n:number){
this.value = n
}
add(n:number){
this.value += n;
return this;
}
multiple(n:number){
this.value *= n;
return this;
}
}

// 多态
class BiggerCalc extends Calc {
sin(){
this.value = Math.sin(this.value);
return this;
}
}

const c = new Calc(1);
c.add(1).multiple(2);

const bc = new BiggerCalc(1);
bc.add(1).multiple(2).sin(3).add(1);
}

this的类型

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
{
function fn(n:number){
console.log(n)
}
fn(1);
fn.call('string',1);

// this 也可以是参数,我怎么指定this的类型
function fn2(this:string,n:number){
console.log(n)
}
// 报错了
// fn2(1); this is undefined
fn2.call('string',1);

// 如何解决这个 不指定this的报错呢? void
function fn3(this: string | void,n:number){
console.log(n)
}
fn3(1);
fn3.call('string',1);
fn3.call(true,1) // 居然也成功了 !!!
// 因为 ts 对 call 不做类型检查

}
  • 这个一般只有写底层库的时候可能用到

索引类型 keyof 和 T[K]

索引类型

假如你有个函数,它的参数是对象 opt = {name:xxx,bbb:xxx,ccc:xxx ...} 可无限扩展的参数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
{
const fn = (options)=>{}

var options = {
time: new Date,
isEdit: true,
size : 20,
name : 'xxx'
}

fn(options)

// 如何声明 options 的类型呢?
interface fnOptions {
// 我的 key 必须是 string ,value 是任意值
[K:string]:any
}
// 以前你要这样
interface fnOptions2{
size:number
name:string
}
const fn2 = (options:fnOptions)=>{}


// T[K] 是啥
// 只能传递 前面有的 key
function pluck<T, K extends keyof T>(objext:T,keys:Array<K>) {
// T - {name:string,age:number,grade:number}
// keyof T - 'name'|'age'|'grade'
// K extends keyof T - 'name'|'age'|'grade'
}


// 第二个参数数组 必须是 第一个参数里的 key 否则报错
pluck({name:'hjx',age:18,grade:100},['name','age'])

interface Person{
name:string;
age:number;
grade:number;
}
type X = keyof Person; // X 只能是 Person里的属性

// T[K]返回值啥意思
function pluck2<T, K extends keyof T>(objext:T,keys:Array<K>): T[K][] {
// 报错,因为 T[K] 里面是对应每个 key的类型 是动态的
// return 'string';
return keys.map(key => objext[key])
}
pluck2({name:'hjx',age:18,grade:100},['name','age'])
/*
['hjx',18]
=>
Array<Person[name]|Person[age]>
=>
Array<T[K]>
=>
T[K][]
*/

}

Readonly / Partial

Readonly

  • Readonly<T>
  • readonly field
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
{
interface Person {
name:string;
age:number;
}

const p :Person = {
name:'aa',
age:12
}
p.name = 'bb';



// Readonly<T> 全部只读
const p2 :Readonly<Person> = {
name:'hjx',
age:18
}
// p2.name = 1; // 报错

interface ReadonlyPerson {
readonly name:string,
age:number;
}

const p3 :ReadonlyPerson = {
name:'hjx',
age:18
}
// p3.name = 1; // 报错
p3.age = 22

}

Partial

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
{
interface Person {
name:string;
age:number;
grade:number;
}

interface Person2 {
name?:string;
age?:number;
grade?:number;
}

type Person3 = Partial<Person>;
// 等价于 Person2


type P4 = Required<Person3>

}

参考代码 ts013

TS入门012ts和React之Class组件

生成 ts-react 项目

1
2
3
4
5
6
7
8
9
mkdir ts-react-demo

cd ts-react-demo

# 创建项目
yarn create react-app . --typescript

# 初始化成功,启动项目
yarn start
  • 移除无用文件
  • 修改 src/index.tsx

    1
    2
    3
    4
    5
    import React from 'react';
    import ReactDOM from 'react-dom';
    import App from './App';

    ReactDOM.render(<App />, document.getElementById('root'));
  • 修改 src/App.tsx

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    import React from 'react';
    import Button from './components/button'

    class App extends React.Component{
    render(){
    return (
    <div className="App">
    <Button>你好</Button>
    </div>
    )
    }
    }

    export default App;
  • 修改 src/components/button.tsx

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    import React from 'react';
    import './button.css';

    class Button extends React.Component{
    render(){
    return (
    <div className="button">{this.props.children}</div>
    )
    }
    }

    export default Button;
  • 修改 src/components/button.css

    1
    2
    3
    4
    5
    6
    7
    8
    9
    .button{
    border:1px solid grey;
    display: inline-block;
    justify-content: center;
    align-items: center;
    padding: 2px;
    border-radius: 4px;
    box-shadow: 0 0 3px black;
    }
  • 此时初步代码完成了

如何控制 Button 的大小? 传递 size

  • 直接传递报错!
    1
    2
    3
    4
    5
    6
    7
    妄图直接传递 size 属性

    <Button size="big">你好</Button>


    不能将类型“{ children: string; size: string; }”分配给类型“IntrinsicAttributes & IntrinsicClassAttributes<Button> & Readonly<{}> & Readonly<{ children?: ReactNode; }>”。
    类型“IntrinsicAttributes & IntrinsicClassAttributes<Button> & Readonly<{}> & Readonly<{ children?: ReactNode; }>”上不存在属性“size”。ts(2322)

泛型的使用

  • 给 Button 通过泛型传递参数列表< size: string >
  • 此时不报错了!!!
  • 如果你想要给 Button 传递参数 就要把属性写到 泛型<>
1
2
3
4
5
6
7
class Button extends React.Component<{ size: string }>{
render(){
return (
<div className="button">{this.props.children}</div>
)
}
}

假如我有10个属性怎么办?

泛型里的内容太多了

1
2
3
4
5
6
class Button extends React.Component<
{ size: string ,
xxx1: string ,
xxx2: string ,
...
}>{...}

你可以这样

1
2
3
4
5
6
7
8
9
10
11
12
13
type Props = {
size: string,
xxx:string,
xxx2:string,
onClick:()=>void
}
class Button extends React.Component<Props>{
render(){
return (
<div className="button">{this.props.children}</div>
)
}
}

你还可以使用接口 interface

1
2
3
4
5
6
7
8
9
10
11
12
13
interface IProps {
size: string,
xxx:string,
xxx2:string,
onClick:()=>void
}
class Button extends React.Component<IProps>{
render(){
return (
<div className="button">{this.props.children}</div>
)
}
}

如果我有 state 怎么办?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
// ? 代表 可选参数 反之必填
interface IProps {
size: string,
xxx?:string,
xxx2?:string,
onClick?:()=>void
}
interface IState {
n : number
}

class Button extends React.Component<IProps,IState>{
// 这里 必须给 props 指定类型 否则报错
constructor(props:IProps){
super(props)
this.state = {
n:1
}
}
render(){
return (
<div className="button">
{this.props.children}

</div>
)
}
}

displayName 是什么

  • 给你组件起的名字,如果你的google 里安装了 react devtools 你的组件就会显示你设置的名字
1
2
3
4
5
6
7
class Button extends React.Component{
// 开发者工具里显示的名字
static displayName = "HjxButton"
render(){
...
}
}

默认 props 值

1
2
3
4
5
6
7
8
9
10
11
interface IProps {
size: string,
}

class Button extends React.Component<IProps>{
// 这里给 size 设置默认值 当你不传递的时候
static defaultProps = {
size : 'normal'
}
...
}

TS 帮你做分析

  • 断言
  • if 判断
  • 规避 js undefined + 1 = NaN 的bug
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
interface IProps {
size: string,
xxx?:string,
xxx2?:string,
onClick?:()=>void
}
interface IState {
n : number
}

class Button extends React.Component<IProps>{
// 这里给 size 设置默认值 当你不传递的时候
static defaultProps = {
size : 'normal',
xxx: 'aaa'
}
constructor(props:IProps){
super(props)
// 这句话报错 因为 xxx 可能是 undefined
// undefined 不能直接和数字 + 操作
// console.log(this.props.xxx + 1)
// 解决办法就是 加一个 “!”
// 就是断言 保证它不会为空 ,出问题自己负责
console.log(this.props.xxx! + 1)
// 或者这样
console.log(this.props.xxx as string + 1)
// 或者还可以这样
if(this.props.xxx === undefined){

}else{
console.log(this.props.xxx + 1)
}
}
render(){
...
}
}

onClick 事件如何写

src/App.tsx

1
2
3
4
5
6
7
8
9
10
11
12
13
class App extends React.Component{
// 参数也要声明类型
onClick(e:React.MouseEvent){
console.log(e)
}
render(){
return (
<div className="App">
<Button onClick={this.onClick}>你好</Button>
</div>
)
}
}

src/components/button.tsx

1
2
3
4
5
6
7
8
9
10
11
12
13
interface IProps {
// 声明它的类型
onClick?: React.MouseEventHandler
}
class Button extends React.Component<IProps>{
render(){
return (
<div className="button" onClick={this.props.onClick}>
{this.props.children}
</div>
)
}
}

我想打印 click 事件源的一些东西

src/App.tsx 里

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
 /*
onClick(e:React.MouseEvent){
console.log(e)
// style 竟然标红了
// 提示 类型“EventTarget”上不存在属性“style”。
//console.log(e.target.style.width)
// 你只能断言
// const div = e.target as HTMLDivElement;
// console.log(div.style.width)

// 断言还是有风险,因为 如果 Button 里传递的是 <span>hi</span>你好
// 断言就有问题了 它不是 div 有可能是 span
}
*/
// 泛型参数 指定触发事件的元素一定是个 div
onClick(e:React.MouseEvent<HTMLDivElement>){
console.log(e.currentTarget)
const div = e.currentTarget;
console.log(div.style.width)
}

state 的一些问题

  • 新建 src/components/button2.tsx
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
import React from 'react';
import './button.css';

interface IProps {
size?: string,
}
interface IState {
n : number
}

class Button2 extends React.Component<IProps,IState>{
constructor(props:IProps){
super(props)
this.state = {
n:1
}
}
onClick(){
this.x();
}
x(){
console.log('x');
}
render(){
return (
<div className="button" onClick={this.onClick}>
{this.props.children}
</div>
)
}
}

export default Button2;

点击后直接报错 因为 this 是 undefined

解决办法:onClick写成箭头函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class Button2 extends React.Component<IProps,IState>{
...
onClick = () => {
this.x();
}
x(){
console.log('x');
}
render(){
return (
<div className="button" onClick={this.onClick}>
{this.props.children}
</div>
)
}
}

还有一种不推荐的方式: bind(this)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class Button2 extends React.Component<IProps,IState>{
...
onClick(){
this.x();
}
x(){
console.log('x');
}
render(){
return (
<div className="button" onClick={this.onClick.bind(this)}>
{this.props.children}
</div>
)
}
}

补充:事件有那些种类

  • React.MouseEventHandler
  • React.KeyboardEventHandler
  • React.TouchEventHandler
  • React.WheelEventHandler

如果你的 props 是 加”?”

  • 使用时候加断言

onClick 的 this问题

  • 箭头函数onClick = () => { ... }
  • bind(this)

参考代码 ts012

TS入门011ts和react应用

TS和React

  • typescript react

    1
    2
    3
    4
    5
    6
    7
    8
    npm i create-react-app@2.1.3 -g

    // 创建工程
    npx create-react-app my-app --typescript

    # or

    yarn create react-app my-app --typescript
  • 进入目录 cd my-app

  • 运行项目 yarn start
  • 成功

Button组件

  • 修改 src/index.tsx

    1
    2
    3
    4
    5
    6
    7
    8
    import React from 'react';
    import ReactDOM from 'react-dom';
    import Button from './Button';

    ReactDOM.render(<div>
    HelloWorld
    <Button />
    </div>, document.getElementById('root'));
  • src/Button.tsx

    1
    2
    3
    4
    import React from 'react';
    export default function Button(){
    return <div className="button">button</div>;
    }
  • 此时一个最简单的 Button 组件完成了

让 Button 接收 size ,为 Button 添加样式

  • src/Button.tsx 里引入 样式 import './Button.css';
1
2
3
4
5
6
7
8
9
10
import React from 'react';
import './Button.css';

interface iProps {
// ?代表 size 可有可无
size?: string
}
export default function Button(props: iProps) {
return <div className={`button ${props.size}`}>button</div>;
}

src/Button.css

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
.button{
border:1px solid grey;
display: inline-block;
border-radius: 4px;
padding: 4px;
}

.button:hover{
background: black;
box-shadow: 0 0 2px black;
color:white;
}

.button.small{
font-size: 12px;
}

.button.big{
font-size: 30px;
}

src/index.tsx

1
2
3
4
5
6
7
8
9
10
import React from 'react';
import ReactDOM from 'react-dom';
import Button from './Button';

ReactDOM.render(<div>
HelloWorld
<Button size="small"/>
<Button/>
<Button size="big"/>
</div>, document.getElementById('root'));

button 的文案显示如何控制?

  • 传递是字符串
  • 传递是 span
  • 传递多个 span
1
2
3
4
5
6
7
8
9
10
ReactDOM.render(<div>
<Button>
<span>Click ME</span>
</Button>
<Button>
<span>Click ME</span>
<span>Click ME</span>
</Button>
<Button>Click Me</Button>
</div>, document.getElementById('root'));

通过props

1
2
3
4
5
6
7
8
9
10
11
12
13
...

interface iProps {
// ?代表 size 可有可无
size?: string;
// 字符串 | 节点 | 节点数组
children?:string | JSX.Element | JSX.Element[];
}
export default function Button(props: iProps) {
return <div className={`button ${props.size}`}>
{props.children}
</div>;
}

以后我定义组件,难道每次都要写一个 children 属性?

  • react 给你提供了便利,你需要这样

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    interface iProps {
    // ?代表 size 可有可无
    size?: string
    }
    const Button:React.FunctionComponent = function (props) {
    return <div className={`button ${props.size}`}>
    {props.children}
    </div>;
    }

    export default Button;
  • FunctionComponent 是什么 vscode 点进去看看

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    interface FunctionComponent<P = {}> {
    (props: PropsWithChildren<P>, context?: any): ReactElement | null;
    propTypes?: WeakValidationMap<P>;
    contextTypes?: ValidationMap<any>;
    defaultProps?: Partial<P>;
    displayName?: string;
    }

    // interface 代表接口 ,那么这个接口满足什么条件呢?
    // 有括号 (props: PropsWithChildren<P>, context?: any) 代表这是一个函数
    interface FunctionComponent<P = {}> {
    (props: PropsWithChildren<P>, context?: any): ReactElement | null;
    ... 后四个代表可选属性
    }

    // 这个函数的特征 接收两个参数 必填的 props 和 可选的 context
    // 同时 props 需要 满足 P 这个东西

    // P 是啥?

    意思就是 TS 类型可以接收参数 用 尖括号“<P = {}>”来写这个参数
    如果你不传递 P 那么这个值的参数就是 空的 => "{}"

    // 同时这个函数的返回值是 “ReactElement” 类型否则报错
  • 改写我们的 Button.tsx 给它传递参数 P 为 iProps 这样我们就可以不写 children

    1
    2
    3
    4
    5
    6
    7
    8
    9
    interface iProps {
    // ?代表 size 可有可无
    size?: string
    }
    const Button:React.FunctionComponent<iProps> = function (props) {
    return <div className={`button ${props.size}`}>
    {props.children}
    </div>;
    }

这就是用react提供的函数组件直接声明一个函数组件

添加点击事件

1
2
3
4
5
6
7
8
9
interface iProps {
size?: string;
// 可有可无 ,而且你要的类型 React帮你做了
onClick?:React.MouseEventHandler
}

// React.MouseEventHandler 是什么 点进去看看
// 一脸懵逼!!! 建议是没用三个月就别点进去看
type MouseEventHandler<T = Element> = EventHandler<MouseEvent<T>>;

修改 button.tsx

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import React, { Children } from 'react';
import './Button.css';

// 第二种方式
interface iProps {
// ?代表 size 可有可无
size?: string;
onClick?:React.MouseEventHandler
}
const Button:React.FunctionComponent<iProps> = function (props) {
return (
<div className={`button ${props.size}`} onClick={props.onClick}
>
{props.children}
</div>);
}

export default Button;

src/index.tsx

1
2
3
4
5
6
7
8
9
10
11
import React from 'react';
import ReactDOM from 'react-dom';
import Button from './Button';

const fn:React.MouseEventHandler = function(e){
console.log(e);
}

ReactDOM.render(<div>
<Button onClick={fn}>Click Me</Button>
</div>, document.getElementById('root'));

参考代码 ts011

TS入门010ts和vue应用

vue ts组件写法参考

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
<template>
<div>
<input v-model="msg">
<p>prop: {{propMessage}}</p>
<p>msg: {{msg}}</p>
<p>helloMsg: {{helloMsg}}</p>
<p>computed msg: {{computedMsg}}</p>
<button @click="greet">Greet</button>
</div>
</template>

<script>
import Vue from 'vue'
import Component from 'vue-class-component'

@Component({
props: {
propMessage: String
}
})
export default class App extends Vue {
// initial data
msg = 123

// use prop values for initial data
helloMsg = 'Hello, ' + this.propMessage

// lifecycle hook
mounted () {
this.greet()
}

// computed
get computedMsg () {
return 'computed ' + this.msg
}

// method
greet () {
alert('greeting: ' + this.msg)
}
}
</script>

直接 google typescript vue

  • 先把官网读完,否则就别继续

创建工程

如果你的是mac 安装的不是 我这版本的 vue 推荐卸载掉vue

1
2
3
4
5
6
7
8
9
1.看vue版本 vue --version
如果不是你想要的版本(3.0以下)执行
要先开放用户权限
sudo chmod -R 777 /usr/local/lib/node_modules/
然后卸载2.0
npm uninstall vue-cli -g
然后 vue --version 确认卸载了

然后装3.0 npm install -g @vue/cli

安装vue-cli3

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
# 1. 如果没有安装 Vue CLI 就先安装 ,我的是3.1.3 版本
npm install --global @vue/cli@3.1.3

# 2. 创建一个新工程,并选择 "Manually select features (手动选择特性)" 选项
vue create my-project-name

# 3. 切换目录
cd my-project-name

# 4. 选择我们的配置
Vue CLI v3.1.3
? Please pick a preset: Manually select features
? Check the features needed for your project: Babel, TS, CSS Pre-processors
? Use class-style component syntax? Yes
? Use Babel alongside TypeScript for auto-detected polyfills? Yes
? Pick a CSS pre-processor (PostCSS, Autoprefixer and CSS Modules are supported
by default): Sass/SCSS
? Where do you prefer placing config for Babel, PostCSS, ESLint, etc.? In dedica
ted config files
? Save this as a preset for future projects? (y/N) n

# 5. 安装成功后,启动这个项目
yarn serve
# 6. 在浏览器打开 http://localhost:8080/

CRM学习

src/App.vue

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
<template>
<div id="app">
props 值为name:{{name}}
Hi TS vue <br>
mixins 值为 : {{mixinValue}} <br>
data里的msg: {{msg}} <br>
<button @click="fn">点我</button> <br>
为 HelloWorld 组件传递值 hjx <br>
<hello-world name="hjx"></hello-world>
</div>
</template>

<script lang="ts">
/*
// 原来的写法
import { Component, Vue } from 'vue-property-decorator';
import HelloWorld from './components/HelloWorld.vue';
@Component({
components:{
HelloWorld
}
})
export default class App extends Vue {
// 原来的 data里的数据
msg = 123

//原来 method 里的函数
fn () {
alert('hello: ' + this.msg)
}
}
*/

// 使用 mixins
import { Component, Vue , Mixins } from 'vue-property-decorator';
import { MyMixin } from './mixins/mixin-one';
import HelloWorld from './components/HelloWorld.vue';
@Component({
components:{
HelloWorld
}
})
export default class App extends Mixins(MyMixin) {
// 原来的 data里的数据
msg = 123

//原来 method 里的函数
fn () {
alert('hello: ' + this.msg)
}
}
</script>

<style lang="scss">
#app {
font-family: 'Avenir', Helvetica, Arial, sans-serif;
color: red;
}
</style>

components/HelloWorld.vue

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<template>
<div class="hello">
我接受到的 props name 为 :{{name}}
</div>
</template>

<script lang="ts">
import { Component, Prop, Vue } from 'vue-property-decorator';

@Component({
props:{
name:String
}
})
export default class HelloWorld extends Vue {
@Prop() private msg!: string;
}
</script>

<style scoped lang="scss">
</style>

使用 mixins

src/mixins/mixin-one.ts

1
2
3
4
5
6
7
8
9
// mixin.js
import Vue from 'vue'
import Component from 'vue-class-component'

// You can declare a mixin as the same style as components.
@Component
export class MyMixin extends Vue {
mixinValue = '111111111111'
}

TodoList 组件

参考代码 ts010

TS入门009_02Vue_ts配置和webpack

去 webpack官网

  • get started
  • 开始抄

    1
    2
    3
    4
    5
    mkdir webpack-demo
    cd webpack-demo
    npm init -y
    npm install webpack@4.22.0 --save-dev
    npm install webpack-cli@3.1.2 --save-dev
  • 新建 src/index.js

    1
    console.log('hi');
  • 新建 webpack.config.js 从 官网抄

    1
    2
    3
    4
    5
    6
    7
    8
    9
    const path = require('path');

    module.exports = {
    entry: './src/index.js',
    output: {
    filename: 'main.js',
    path: path.resolve(__dirname, 'dist')
    }
    };
  • npx webpack 意思你要声明 mode

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    Hash: 8b2187a6e37dccc947c6
    Version: webpack 4.39.1
    Time: 322ms
    Built at: 2019-08-11 18:53:08
    Asset Size Chunks Chunk Names
    main.js 978 bytes 0 [emitted] main
    Entrypoint main = main.js
    [0] ./src/index.js 50 bytes {0} [built]

    WARNING in configuration
    The 'mode' option has not been set, webpack will fallback to 'production' for this value. Set 'mode' option to 'development' or 'production' to enable defaults for each environment.
    You can also set it to 'none' to disable any default behavior. Learn more: https://webpack.js.org/configuration/mode/
  • 添加 mode

    1
    2
    3
    4
    5
    module.exports = {
    ...
    mode:"development",
    ...
    };
  • 再次运行 npx webpack 此时没警告了

  • 我们需要两个配置,一个是 dev 一个是 prod

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    // 新建 webpack.prod.js 内容和 webpack.config.js 区别就是 mode 不一样
    const path = require('path');
    module.exports = {
    entry: './src/index.js',
    mode:"production",
    output: {
    filename: 'main.js',
    path: path.resolve(__dirname, 'dist')
    }
    };
  • 需改 package.json

    1
    2
    3
    4
    5
    6
    7
    "scripts": {
    "build:dev": "webpack",
    "build": "webpack --config webpack.prod.js",
    }

    // npm run build:dev 就是编译 不压缩
    // npm run build 就是编译 压缩
  • 搞出一个页面来显示,那么就需要 webpack-dev-server

    1
    npm install webpack-dev-server@3.1.9 --save-dev
  • npx webpack-dev-server 还无法显示 因为你需要 webpack html plugin

    1
    npm i --save-dev html-webpack-plugin@3.2.0
  • 如何使用 html-webpack-plugin 看 usage 然后抄

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    const HtmlWebpackPlugin = require('html-webpack-plugin')

    module.exports = {
    entry: 'index.js',
    output: {
    path: __dirname + '/dist',
    filename: 'index_bundle.js'
    },
    plugins: [
    new HtmlWebpackPlugin()
    ]
    }
  • webpack.config.js

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    const path = require('path');
    const HtmlWebpackPlugin = require('html-webpack-plugin')

    module.exports = {
    entry: './src/index.js',
    mode:"development",
    output: {
    filename: 'main.js',
    path: path.resolve(__dirname, 'dist')
    },
    plugins: [
    new HtmlWebpackPlugin()
    ]
    };
  • 修改 index.js

    1
    2
    3
    4
    5
    6
    console.log("Hello World from index main file!");
    let div = document.createElement('div');
    div.id = 'app';
    div.textContent = "hjx webpack";

    document.body.appendChild(div);
  • 运行 npx webpack-dev-server

使用Vue

  • src/Hjx.vue

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    <template>
    <div>I am Hjx,{{msg}}</div>
    </template>

    <script>
    export default {
    name:'hjx',
    data(){
    return {
    msg:'你好!'
    }
    }
    }
    </script>
    <style>
    </style>
  • 安装 vue 运行 npm i vue@2.5.17

  • 安装 vue loader

    1
    npm install -D vue-loader@15.4.2 vue-template-compiler@2.5.17
  • 修改 webpack.config.js

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    const path = require('path');
    const HtmlWebpackPlugin = require('html-webpack-plugin')
    const VueLoaderPlugin = require('vue-loader/lib/plugin')

    module.exports = {
    entry: './src/index.js',
    mode:"development",
    output: {
    filename: 'main.js',
    path: path.resolve(__dirname, 'dist')
    },
    module: {
    rules: [
    {
    test: /\.vue$/,
    loader: 'vue-loader'
    }
    ]
    },
    plugins: [
    new HtmlWebpackPlugin(),
    new VueLoaderPlugin()
    ]
    };
  • npx webpack-dev-server 终于成功了

配置ts

但是不知道怎么配置,借助 google

搜 typescript loader

得到两个答案,只能挨个试

ts-loader

  • npm install ts-loader@5.2.2 -D
  • npm install typescript --save-dev3.1.3 -D
  • 修改 webpack.config.js

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    const path = require('path');
    const HtmlWebpackPlugin = require('html-webpack-plugin')
    const VueLoaderPlugin = require('vue-loader/lib/plugin')

    module.exports = {
    entry: './src/index.js',
    mode:"development",
    output: {
    filename: 'main.js',
    path: path.resolve(__dirname, 'dist')
    },
    // 默认允许加载ts / tsx 结尾的东西
    resolve: {
    //
    extensions: [".ts", ".tsx", ".js"]
    },
    module: {
    rules: [
    {
    test: /\.vue$/,
    loader: 'vue-loader'
    },
    { test: /\.tsx?$/, loader: "ts-loader" }
    ]
    },
    plugins: [
    new HtmlWebpackPlugin(),
    new VueLoaderPlugin()
    ]
    };
  • 新建 tsconfig.json

    1
    2
    3
    4
    5
    {
    "compilerOptions": {
    "sourceMap": true
    }
    }
  • 新建 src/test.ts

    1
    2
    const a: number = 1;
    export const b = a;
  • 修改 src/index.js

    1
    2
    import {b} from './test.ts';
    console.log(b);
  • 再次运行 npx webpack-dev-server 成功了,此时我们的配置 既支持vue又支持 ts

修改我们的入口文件 index.js 为 index.ts

  • webpack.config.js 里的入口修改
  • 此时运行npx webpack-dev-server报错了

    1
    2
    3
    4
    5
    6
    Cannot find module './Hjx.vue'.
    // 没改之前可以,改了反而不行了。

    // 有没有可能是 ts 找不到,一定是配置有问题

    // 参考上篇的vue-cli 生成的 ts配置
  • 修改 tsconfig.js ,然后运行 npx webpack-dev-server 还是报那个错

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    {
    "compilerOptions": {
    "sourceMap": true,
    "baseUrl": ".",
    },
    "include": [
    "src/**/*.ts",
    "src/**/*.tsx",
    "src/**/*.vue",
    "tests/**/*.ts",
    "tests/**/*.tsx"
    ],
    "exclude": [
    "node_modules"
    ]
    }
  • 只能借助 google 了 typescript cannot find module vue

    1
    2
    3
    // 大概意思就是 没有声明文件
    // 继续参考vue-cli 生成的声明文件
    This is because you don't have declarations of .vue files, then the typescript compiler cannot load them. You need to declare general declaration of .vue file in your project or generating each .vue file declaration by using vuetype
  • 新建 shims-vue.d.ts

    1
    2
    3
    4
    declare module '*.vue' {
    import Vue from 'vue'
    export default Vue
    }
  • 运行 npx webpack-dev-server 此时 我们就可以脱离js了

在vue组件里使用 ts

  • vue loader typescript
  • 找到一个 riku179 的答案

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    module: {
    rules: [
    {
    test: /\.js/,
    exclude: /node_modules/,
    loader: 'babel-loader',
    options: {
    // presets: ['es2015', { modules: false }] <- fail
    presets: ['es2015']
    }
    },
    {
    test: /\.ts$/,
    exclude: /node_modules|vue\/src/,
    use: [
    {
    loader: 'ts-loader',
    options: {
    appendTsSuffixTo: [/\.vue$/]
    }
    },
    ]
    },
    {
    test: /\.vue$/,
    loader: 'vue-loader',
    options: {
    esModule: true,
    }
    },
    ]
    }
  • 修改我们的 webpack.config.js

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    module: {
    rules: [
    {
    test: /\.vue$/,
    loader: 'vue-loader',
    options: {
    esModule: true,
    }
    },
    {
    test: /\.tsx?$/, loader: "ts-loader",
    options: {
    appendTsSuffixTo: [/\.vue$/]
    }
    }
    ]
    }
  • src/Hjx.vue

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    <template>
    <div>
    {{number}}
    <button @click="add">+</button>
    <button @click="minus">-</button>
    </div>
    </template>

    <script lang="ts">
    export default {
    name:'hjx',
    data(){
    return {
    number:0,
    }
    },
    methods:{
    add(){
    let number:number = this.number + 1;
    this.number = number;
    },
    minus(){
    let number:number = this.number - 1;
    this.number = number;
    }
    }
    }
    </script>
    <style>
    </style>
  • 成功~

所有都是通过 google 没有任何书和文档说明这些,所以只能通过细节一个一个推敲

代码

TS入门009_01Vue_ts配置cli版本

直接上官网

  • https://vuejs.org/
  • 找到 TS 支持,我的是 3.0版本

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    # 1. Install Vue CLI, if it's not already installed
    npm install --global @vue/cli

    # 2. Create a new project, then choose the "Manually select features" option
    vue create my-project-name

    # 提示你是否在当前目录生成 y
    Vue CLI v3.0.0
    ? Generate project in current directory? (Y/n)
    # 是否选择预置的? 选 Manually
    Vue CLI v3.0.0
    ? Please pick a preset: (Use arrow keys)
    ❯ default (babel, eslint)
    Manually select features

    # 选择你要的内容 router / vuex 做项目可以选上,这里必须的是 ts / babel
    ? Check the features needed for your project:
    ◉ Babel
    ◉ TypeScript
    ◯ Progressive Web App (PWA) Support
    ◯ Router
    ◯ Vuex
    ❯◉ CSS Pre-processors
    ◉ Linter / Formatter
    ◯ Unit Testing
    ◯ E2E Testing

    ? Use class-style component syntax? Yes
    ? Use Babel alongside TypeScript for auto-detected polyfills? Yes
    ? Pick a CSS pre-processor (PostCSS, Autoprefixer and CSS Modules are supported
    by default): SCSS/SASS
    ? Where do you prefer placing config for Babel, PostCSS, ESLint, etc.? In dedica
    ted config files
    # 是否以后都用这个当作默认配置
    ? Save this as a preset for future projects? (y/N) n
  • 创建好项目 yarn serve

来看 app.vue

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
<template>
<div id="app">
<img alt="Vue logo" src="./assets/logo.png">
<HelloWorld msg="Welcome to Your Vue.js + TypeScript App"/>
</div>
</template>

<script lang="ts">
// 这是 js 语法
import { Component, Vue } from 'vue-property-decorator';
// 这是 js 语法
import HelloWorld from './components/HelloWorld.vue';


// 这里是新的组件写法 装饰器语法
@Component({
components: {
HelloWorld,
},
})
export default class App extends Vue {}
</script>

<style lang="scss">
...
</style>
  • 新建我们的组件 Hjx

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    <template>
    <div>
    I am hjx!
    <HelloWorld msg="Welcome to Your Vue.js + TypeScript App"/>
    </div>
    </template>
    <script lang="ts">
    import Vue from 'vue'
    import Component from 'vue-class-component';
    import HelloWorld from './HelloWorld.vue';

    @Component({
    components: {
    HelloWorld
    },
    })
    export default class Hjx extends Vue{}
    </script>
  • App.vue 里 使用 它

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    <template>
    <div id="app">
    <img alt="Vue logo" src="./assets/logo.png">

    <Hjx/>
    </div>
    </template>

    <script lang="ts">
    import { Component, Vue } from 'vue-property-decorator';
    import Hjx from './components/Hjx.vue';

    @Component({
    components: {
    Hjx
    },
    })
    export default class App extends Vue {}
    </script>

    <style lang="scss">
    #app {
    font-family: 'Avenir', Helvetica, Arial, sans-serif;
    -webkit-font-smoothing: antialiased;
    -moz-osx-font-smoothing: grayscale;
    text-align: center;
    color: #2c3e50;
    margin-top: 60px;
    }
    </style>

代码

ZB-022-01超详细网址输入url过程

浏览器输入url过程

  1. 当你打开一个网页的时候,这个世界上一定有一个地方处理这个请求,这个页面不可能凭空掉下来。
  2. 这个地方在那里呢?

这就不可避免的说:计算机网络的基础架构

计算机网络的基础架构

在互联网上有很多很多的计算机,假设它们之间用网线连接起来。

通过运营商 联通/电信 连接到你家的小区里,然后在小区里跟你家里牵根线。此时你躺在床上拿着手机。

你的手机通过无线路由器,和这个网络相连的。

一个概念

在计算机世界里有很多很多的电脑,这个电脑就叫主机 HOST ,和你的笔记本没什么区别。把你的笔记本放机房里也可以充当一台主机。

因此,这个世界上有很多很多相连的计算机,我们称之为主机

每个主机有一个IP地址。

在IPV4的世界里,IP地址是 32位地址(4个字节),形如 192.118.112.3,IP地址就是网络上计算机的地址

这个地址可以唯一的定位一台计算机就是主机

1个byte -128~127, 而无符号的byte最大值刚好是 255。

因此IP地址最大不超过255

32位可以存多少数字呢?

42亿个数字,也就是说这个世界上最多有42亿IPV4的IP地址,但是发展到现在 发现42亿的IP地址果然不够用。于是有了IPV6

但是,无论访问什么网站,我们都不会用IP地址

因为IP地址难记啊!

如果不难,请问 百度/淘宝 的IP地址是什么?

有一天你躺在床上在QQ上和妹子聊天

  • 你的手机必须知道和这个世界上一台主机(HOST)在通信,所以你必须知道这个主机的IP地址,但是IP地址很难记。

我们需要另一种服务DNS

DNS Domain Name Service 域名服务

当你网址输入 www.taobao.com的时候,

  1. 如果计算机不知道淘宝的主页在哪里,它就会向 DNS 请求查询 taobao.com
  2. dns会返回一个IP地址给你
  3. 此时通过这个地址你就可以访问淘宝了

本地HOST

如果你本地保存有 一个淘宝的映射,那么请求taobao.com的时候,就不会去查找DNS了

mac的host文件在 /etc/hosts,当你做如下修改的时候。

1
127.0.0.1   www.taobao.com

你再次访问 taobao.com 时,会直接到 127.0.0.1

端口

一台机子有很多的端口最多有65536个端口,如 mysql 3306。

在网线上传输的数据,每一个数据的数据包里面都指定了一个端口,指名和对应的端口通信。

HTTPS协议的默认端口是 443

HTTP协议的默认端口是 80

意思就是 你输入 http://www.taobao.comhttp://www.taobao.com:80 没区别

相似的 你输入 https://www.taobao.comhttps://www.taobao.com:443 没区别

端口指定了你通信的主机的归属地

更进一步,为什么输入网址后能看到绚丽多彩的页面呢?

最基本的通信协议有两种

  • TCP 传输控制协议
  • UDP 用户数据包协议(http协议里一般不用,不再详述)

TCP 可以想像成一条双向的高速公路,公路上跑的是数据

TCP 是基于 Stream 流的协议

TCP 是一个 全双工协议 ,比如 打电话,双方都可以同时说话。

半双工协议——对讲机。每个时刻只有一个对讲机说话。

TCP的本质就是

定义了字节流在网络上如何发送和接收

在TCP之上的协议——HTTP HyperText Transfer Protocol

HTTP就是丰富网络世界的基石,它定义了文本之外的东西如何被传输。

  • request
  • response

一个网址如何变成美轮美奂的页面呢?

浏览器是如何工作的

当你访问 www.taobao.com 时候,响应给你如下内容,但是页面却显示的是美轮美奂的页面

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="utf-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1" />
<meta name="renderer" content="webkit" />
<title>淘宝网 - 淘!我喜欢</title>
<meta name="spm-id" content="a21bo" />
<meta name="description" content="淘宝网 - 亚洲较大的网上交易平台,提供各类服饰、美容、家居、数码、话费/点卡充值… 数亿优质商品,同时提供担保交易(先收货后付款)等安全交易保障服务,并由商家提供退货承诺、破损补寄等消费者保障服务,让你安心享受网上购物乐趣!" />
<meta name="aplus-xplug" content="NONE">
<meta name="keyword" content="淘宝,掏宝,网上购物,C2C,在线交易,交易市场,网上交易,交易市场,网上买,网上卖,购物网站,团购,网上贸易,安全购物,电子商务,放心买,供应,买卖信息,网店,一口价,拍卖,网上开店,网络购物,打折,免费开店,网购,频道,店铺" />
<link rel="dns-prefetch" href="//ald.taobao.com" />
<link rel="dns-prefetch" href="//gw.alicdn.com" />

<div>
。。。
</div>
</body>
</html>

当浏览器看到一个标签<html lang="zh-CN">的时候,就知道这是一个文档树,它会把它解析成一颗HTML树,然后决定它如何被显示。

HTML 规定了很多东西

  • 如 link 是一个 css
  • 如 script 是一段 js

当浏览器遇到如上内容的时候,浏览器就开始解析它,每当看到一个 link 就知道要去下载这个 css ,CSS规定了页面如何渲染,比如两个按钮之间的间距,背景色。

当这个 link 解析完成之后,就去解析下一个标签。

当浏览器看到 script的时候,它就知道自己要执行一段js代码。

当浏览器看到 img的时候,它就知道去把这个图片下载下来然后把它放在页面的某个位置上去。这就是为什么你访问 www.taobao.com 的时候,会发送许多许多你不知道的请求。

因为解析 如上 文档树的时候,发现里面还有许多许多东西需要加载,于是它就循环往复这个过程发起一个请求,拿到响应,直到把所有的事情都做完

一个问题,是整个HTML下载完毕就去解析,还是来一点响应就解析一点

  • 答案是 来一点解析一点

同步加载和异步加载

  • 浏览器一次返回所有的数据
  • 浏览器返回部分数据,使用 ajax 异步加载

只要浏览器能做到的,其他代码一定能做到

ReactWheels07-03Hooks全解4

代码仓库

自定义Hooks

useList.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
import { useState, useEffect } from "react";

const useList = () => {
const [list, setList] = useState(null);
useEffect(() => {
ajax("/list").then(list => {
setList(list);
});
}, []); // [] 确保只在第一次运行
return {
list: list,
addItem: name => {
setList([...list, { id: Math.random(), name: name }]);
},
deleteIndex: index => {
setList(list.slice(0, index).concat(list.slice(index + 1)));
}
};
};
export default useList;

function ajax() {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve([
{ id: "1", name: "Frank" },
{ id: "2", name: "Jack" },
{ id: "3", name: "Alice" },
{ id: "4", name: "Bob" }
]);
}, 2000);
});
}

HooksDemo.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
import React from 'react';
import useList from "../hooks/useList";

function HooksDemo() {
const { list, deleteIndex } = useList();
return (
<div className="App">
<h1>List</h1>
{list ? (
<ol>
{list.map((item, index) => (
<li key={item.id}>
{item.name}
<button
onClick={() => {
deleteIndex(index);
}}
>
x
</button>
</li>
))}
</ol>
) : (
"加载中..."
)}
</div>
);
}

export default HooksDemo;

Stale Closure

ReactWheels07-03Hooks全解3

代码仓库

Memo

  • React.Memo
  • Child3 可以用 React.Memo(Child)代替
  • 如果props不变,就不会再次执行一个函数组件
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
import React, { useState } from 'react';
function UseMemoDemo1() {
const [n, setN] = useState(0)
const [m, setM] = useState(0)
const onChangeN = () => {
setN(i => i + 1)
}
const onChangeM = () => {
setM(i => i + 1)
}
return (
<div>
没有缓存,Child组件 依赖的是m,点击按钮更新的是n,也会触发 Child从新执行
<br />
n:{n},
<br />
m:{m}
<br />
<button onClick={onChangeN}>onChangeN</button>
<br />
<button onClick={onChangeM}>onChangeM</button>
<Child data={m} />
<Child3 data={m} />
</div>
)
}

function Child(props) {
console.log('Child执行了')
console.log('Child内大量逻辑1')
console.log('Child内大量逻辑2')
console.log('Child内大量逻辑3')
return (<div>child:{props.data}</div>)
}

function Child2(props) {
console.log('Child2执行了')
console.log('Child2内大量逻辑1')
console.log('Child2内大量逻辑2')
console.log('Child2内大量逻辑3')
return (<div>child2:{props.data}</div>)
}

// 被缓存,第一次执行后,当依赖的值变了才会更新
const Child3 = React.memo(Child2)

export default UseMemoDemo1;

Memo的Bug: 如果 传递一个props.fn

  • 每次渲染的时候 父组件的 fn会重新生成一个,这样就导致,props变了, memo的组件失效了
  • 解决办法就是:useMemo
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
import React, { useCallback, useMemo, useState } from 'react';
// useMemo
function UseMemoDemo2() {
const [n, setN] = useState(0)
const [m, setM] = useState(0)
const onChangeN = () => {
setN(i => i + 1)
}
const onChangeM = () => {
setM(i => i + 1)
}
const onClickChild = () => { } // 这个每次执行的时候 都会重新生成一个 而且是引用的地址 ,child依赖它,它变了,child无法被缓存

/*
useMemo(()=>{
return 复用的函数
},[依赖的变量])
这个函数复用
return 就是复用的函数
第二个参数里的 [] 内是依赖变化的值
*/
const onClickChildMemo = useMemo(() => {
// return的东西 就是你要缓存的函数
return () => { }
}, [m]) // []里是依赖的东西变了才更新

/*
useCallback(fn,[m])
等价于
useMemo(()=>fn,[m])
*/
const fn = () => console.log(m)
const onClickChildMemo2 = useMemo(() => fn, [m])
const onClickChildMemo3 = useCallback(fn, [m])
return (
<div>
没有缓存,Child组件 依赖的是m,点击按钮更新的是n,也会触发 Child从新执行
<br />
n:{n},
<br />
m:{m}
<br />
<button onClick={onChangeN}>onChangeN</button>
<br />
<button onClick={onChangeM}>onChangeM</button>
<Child2 data={m} onClick={onClickChild} />
<Child4 data={m} onChilk={onClickChildMemo} />
</div>
)
}

function Child(props) {
console.log('Child执行了')
console.log('Child内大量逻辑1')
console.log('Child内大量逻辑2')
console.log('Child内大量逻辑3')
return (<div onClick={props.onClick}>child:{props.data}</div>)
}

const Child2 = React.memo(Child)

function Child3(props) {
console.log('Child3执行了')
console.log('Child3内大量逻辑1')
console.log('Child3内大量逻辑2')
console.log('Child3内大量逻辑3')
return (<div onClick={props.onClick}>child:{props.data}</div>)
}

const Child4 = React.memo(Child3)

export default UseMemoDemo2;

useMemo

特点

  • 第一个参数是 ()=>value
  • 第二个参数是 依赖 [m,n]
  • 只有依赖变了的时候,才会计算出新的 value,反之复用
1
2
3
4
5
6
7
8
9
10
11
12
/*
useMemo(()=>{
return 复用的函数
},[依赖的变量])
这个函数复用
return 就是复用的函数
第二个参数里的 [] 内是依赖变化的值
*/
const onClickChildMemo = useMemo(() => {
// return的东西 就是你要缓存的函数
return () => { }
}, [m]) // []里是依赖的东西变了才更新

注意

  • 如果你的 value是个函数你就要这样
1
useMemo(()=>(x)=>{console.log(1)}},[依赖1,依赖2])
  • useCallback的优化
1
2
3
4
5
6
7
8
9
10
// 两个箭头看着很懵逼
useMemo(()=>(x)=>{console.log(x)}},[依赖1,依赖2])

// useCallback 你可以这样
useCallback((x)=>{console.log(x)}},[依赖1,依赖2])


useCallback(fn,[m])
等价于
useMemo(()=>fn,[m])

useRef

  • 当你需要一个值,在组件不断render的时候保持不变(addr)
  • 初始化 const count = useRef(0)
  • 读取 count.current
  • 为什么需要 current?
    • 为了保证多次 useRef 都是同一个值(只有引用能做到)
    • count = {current:0} 地址101
    • 下次 +1 后 count = {current:1} 地址101

扩展vue3内容

  • vue3的ref 改变ref的值,会触发UI更新

    1
    2
    3
    4
    5
    6
    // 初始化
    cosnt count = ref(0)
    // 读取
    count.value
    // 更新,它会触发UI更新,vue3会自动 render
    count.value +=1
  • 而 react的 ref不会触发 UI更新

想要给React的ref改变的时候触发UI更新咋办?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
import React, { useEffect, useRef, useState } from 'react';
function UseRefDemo1() {
const count = useRef(0)
const [_, set_] = useState(null)
const onChangeN = () => {

count.current += 1
}
const onChangeNUpdatedUI = () => {
count.current += 1
set_(Math.random())
}
useEffect(() => {
console.log(count)
})
return (
<div>
count:{count.current},
<br />
<button onClick={onChangeN}>onChangeRef不更新UI</button>
<button onClick={onChangeNUpdatedUI}>onChangeRef更新UI</button>
</div>
)
}

export default UseRefDemo1;

forwardRef : 仅限函数组件

  • props无法传递 ref
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import React, { useRef } from "react";
import ReactDOM from "react-dom";

function App() {
const buttonRef = useRef(null);
return (
<div className="App">
<Button2 ref={buttonRef}>按钮</Button2>
{/* 看浏览器控制台的报错 */}
</div>
);
}

const Button2 = props => {
return <button className="red" {...props} />;
};

const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
  • 实现ref传递
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
import React, { useRef } from "react";
import ReactDOM from "react-dom";

import "./styles.css";

function App() {
const buttonRef = useRef(null);
return (
<div className="App">
<Button3 ref={buttonRef}>按钮</Button3>
</div>
);
}

const Button2 = (props,ref)=>{
return <button className="red" ref={ref} {...props} />;
}

// React.forwardRef 可以额外多传递一个参数
// 把ref参数 转发给 Button2
const Button3 = React.forwardRef(Button2);

const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);

useRef

  • 可用来引用DOM,避免来 docuemt.querySelector
  • 也可以引用普通对象

forwardRef

  • 由于 props不能传递 ref,所以需要 forwardRef
  • 为什么props不包含 ref呢?,因为大部分时候不需要

useImperativeHandle

  • 名字起的稀巴烂
  • 应该叫做 setRef
  • 用于自定义 ref的属性
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
import React, {
useRef,
useEffect,
useImperativeHandle,
createRef
} from "react";

function SetRefDemo() {
const buttonRef = useRef(null);
useEffect(() => {
console.log(buttonRef.current);
});
return (
<div className="App">
<Button2 ref={buttonRef}>按钮</Button2>
<button
className="close"
onClick={() => {
console.log(buttonRef);
buttonRef.current.x();
}}
>
</button>
</div>
);
}

const Button2 = React.forwardRef((props, ref) => {
const realButton = createRef(null);
const setRef = useImperativeHandle;
// 对 ref进行自定义的包装返回
setRef(ref, () => {
return {
x: () => {
realButton.current.remove();
},
realButton: realButton
};
});
return <button ref={realButton} {...props} />;
});

export default SetRefDemo;