一、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组件 |
|---|---|---|
| state | class组件可以定义自己的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 都会调用 <===> 等同于:
componentDidMount和componentDidUpdateuseEffect(()=>{ //TODO //具体逻辑 }) -
传入一个空数组,只会调用一次, <===> 等同于:
componentDidMount和componentDidUpdateuseEffect(()=>{ //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> ) }