Ts+React 、React Hooks笔记

128 阅读5分钟

一、Ts+React 项目初始化

yarn create react-app 项目名称 --template typescript

然后进入项目并启动

cd 项目名称/
yarn start

​ ​ 运行npm run eject运行报错,可以这样配置 ​

git init
git add .
git commit -m 'init'
npm run eject

二、Ts在React中使用的一些基本用法

1、 .ts、.tsx

// js->ts
// jsx->tsx

2、.ts、.d.ts

// .ts 普通文件
// .d.ts 是声明文件

三、State、Props

1、Class组件写法

  • Component<IProps, IState>若该组件没有定义IProps,传{}对象即可
  • Component<{}, IState>
import React from "react";
import Hello from './components/Hello';

function App() {
  return (
    <div className="App">
       <Hello title="测试标题" age={20} />
    </div>
  );
}

export default App;
//组件部分
import React , {Component} from "react";

// 定义接口
interface IProps {
    title: string;
    age?: number;
}

interface IState {
    count: number;
}

export default class Hello extends Component<IProps, IState>{
    // 实现state
    public readonly state: Readonly<IState>={
        count: 1000,
    };  
    public handleChange = () =>{
        // alert('ok!');
        this.setState({count:3000})
    }

    render() {
        const {title,age}=this.props;
        const {count}=this.state;
        return(
            <div>
                <h2>Hello:{title}===age:{age}</h2>
                <div>
                    <h3>{count}</h3>
                    <button onClick={this.handleChange}>测试按钮</button>
                </div>
            </div>
        );
    };

}

2、Functional组件写法

  • 如果IProps里有对象属性,可以再声明个接口描述这个对象
  • 函数组件里React.FC<>这里不需要传IState,因为函数组件里是使用hooks的
import React from "react";
import { TextField } from './components/TextField';

function App() {
  return (
    <div className="App">
       {/* <Hello title="测试标题" age={20} /> */}
       <TextField
        text="Hello"
        person={{ firstName: 'Chenxii', lastName: 'C' }}
      />
    </div>
  );
}

export default App;
// 组件部分
import React from "react";

interface IPerson {
    firstName: string;
    lastName: string; 
}
interface IProps {
    text: string;
    ok?:boolean;
    index?: number;
    fn?:(bob: string) => void;
    person?: IPerson
}

export const TextField: React.FC<IProps> = (props) => {
    return <div>{props.text}</div>;
};

3、class与function组件对比

对比class组件function组件
stateclass组件可以定义自己的state
用来保存组件自己内部的状态
函数式组件不可以,因为函数
每次调用都会产生新的临时变量
生命周期class组件有自己的生命周期,我们可以在
对应的生命周期中完成自己的逻辑
每次重新渲染都会重新发送一次网路请求
render渲染class组件可以在状态改变时「只会」
重新执行render函数
函数式组件在重新渲染时,整个函数都会被执行

四、React Hooks 包含的10钩子

  • 参考文献
  • 基础
    • useState
    • useEffect
    • useContext
  • 高级
    • useReducer
    • useCallback
    • useMemo
    • useRef
    • useImperativeHandle
    • useLayoutEffect
    • useDebugValue

1、useState

基本语法

const [state,setState]=useState(initialState)
  • 传入的唯一参数:initialState,可以是

    • 数字 useState(0)
    • 字符串
    • 对象 useState({a:1})
    • 数组 useState[1,2]
    • 可以传入函数,来通过逻辑计算出默认值
  • 返回的是包含两个元素的数组,

    • 第一个state 变量
    • 第二个 setState 是修改state值的方法,是一个函数
  • 看个🌰

    import React, { useState} from "react";
    const [count, setCount]= useState(0)
    
    return(
      <div>
        点击次数:{count}
        <button  onClick={() => { setCount(count + 1)}}>点我</button>
      </div>
    )
    

    上面例子有些需要注意的地方

    • 传入相同值,是不会重新渲染的
    • 组件每渲染一次,useState中函数其实不会每次都执行
    • setUseState时获取上一轮值
    setCount(count=>count+1)
    
    • 多个useState的情况,尽量不要在循环,条件或嵌套函数中调用hook,必须确保是在React函数的最顶层调用。确保hook在每一次渲染中都按照同样的顺序被调用。

2、useEffect

基本描述

  • 主要提供了页面钩子方法,完成一些类似于class组件中生命周期
    componentDidMount 挂载
    componentDidUpdate 更新
    componentWillUnmount 卸载
    的功能
  • 例如像:网络请求、手动更新DOM、一些事件的监听,都是React更新DOM的一些副作用
  • 举个🌰
import React, { useEffect} from "react";
useEffect(()=>{
  console.log('useEffect被执行了')
})

基本用法

  • 作用:通过useEffect的Hook,告诉React需要在渲染后执行某些操作

  • 参数useEffect要求我们传入一个回调函数,在React执行完更新DOM操作之后,就会回调这个函数

  • 执行时机:首次渲染之后,或者每次更新状态之后,都会执行这个回调函数 引起注意的是,useEffect的第二个参数

  • 什么都不传,组件每次 render 之后 useEffect 都会调用 <===> 等同于:componentDidMountcomponentDidUpdate

     useEffect(()=>{
       //TODO
       //具体逻辑
     })
    
  • 传入一个空数组,只会调用一次, <===> 等同于:componentDidMountcomponentDidUpdate

    useEffect(()=>{
      //TODO
      //具体逻辑
    },[])
    
  • 传入一个数组,其中包括变量,只有这些变量变动时,useEffect才会执行

    useEffect(()=>{
      //TODO
      //具体逻辑
    },[count])
    

🤔 有些时候需要清除Effect

最常见的场景:清除订阅外部数据源,防止引起内存泄露

参考代码如下

import React, { useEffect, useState } from 'react'
useEffect(() => {
  // 默认情况下,每次渲染后都会被调用该函数
  console.log('订阅一些事件')
  
  // 如果要实现 componentWillUnmount,
  // 在末尾处返回一个函数
  // React 在该函数组件卸载前调用该方法
  // 其命名为cleanup 是为了表明此函数的目的
  // 但其实也可返回一个箭头函数或者起一个别名
  return function cleanup() => {
    console.log('取消订阅')  //取消订阅
  }
})
  • 为啥要在Effect中返回一个函数?

    • 这是因为Effect可选的清除机制,每个Effect都可返回一个清除函数

    • 如此可以将添加和移除订阅的逻辑放到一起

    • 都是属于Effect的一部分

🤔 可以同时使用多个Effect

解决目的:解决class中生命周期经常将很多的逻辑放在一起的问题

  • 网络请求
  • 事件监听
  • 手动修改DOM
  • ......
useEffect(() => {
  console.log('修改DOM')
})
useEffect(() => {
  console.log('订阅')
},[])
useEffect(() => {
  console.log('网络请求')
},[])

3、useLayoutEffect

区别

  • useEffect会将渲染的内容更新到DOM上后执行,不会阻塞DOM更新
  • useLayoutEffect会将渲染的内容更新到DOM上之前执行,会阻塞DOM更新

使用场景

  • 希望在执行某些操作之后再DOM,就可使用useLayoutEffect,页面会有阻塞
import React, { useState ,useLayoutEffect ,useEffect} from "react";
export const TextField: React.FC<IProps> = (props) => {
    const [count, setCounter] = useState(10)
    useLayoutEffect(() => {
        if(count === 0){
            setCounter(Math.random())
        }
    },[count])
    return(
        <div>
            <h2>数字:{count}</h2>
            <button onClick={e=>setCounter(0)}>测试</button>
        </div>
    )
};

4、useRef

  • 基本用法

    • useRef返回一个ref对象,返回的ref对象在组件的整个生命周期保持不变
    const refContainer=useRef(initialState)
    
    const refContainer=useRef(0)
    // 返回结果
    {current: 0}
    
  • 常见的使用场景

    • 保存一个数据,这个对象在整个生命周期可以保持不变
    const [count, setCount] = useState(0);
    const numRef = useRef(count);
    
    // 将上次的count进行保存,在count发生改变时,重新保存count
    // 在点击button时,增加count时,会调用useEffect函数,渲染DOM后,会重新将上一次的值进行保存,使用ref保存上一次的某一个值不会触发render
    useEffect(() => {
      numRef.current = count;
    }, [count]);
    
    <div>count 上次的值:{numRef.current}</div>
    <div>count 这次的值:{count}</div>
    <button onClick={(e) => setCount(count + 10)}>+10</button>
    
    • 引入DOM(或者组件)元素,但是前提条件需要是class组件
    class childTest extends React.Component {
      render() {
        return <div>childTest</div>
      }
    }
    
    export default function demo() {
      const titleRef = useRef()
      const cpnRef = useRef()
    
      function changeDOM() {
        // 修改DOM
        titleRef.current.innerHTML = 'hello world'
        console.log(cpnRef.current)
      }
    
      return (
        <div>
          {/* 1.修改DOM元素 */}
          <h2 ref={titleRef}>RefHookDemo01</h2>
          {/* 2.获取class组件 */}
          <ChildCpn ref={cpnRef} />
          <button onClick={changeDOM}>修改DOM</button>
        </div>
      )
    }