React18学习笔记(六) React中的类组件,极简的状态管理工具zustand,React中的Typescript

一.React中的类组件

写在前面:

React的早期版本都是使用Class组件来编写代码的,因此类组件的学习仅限了解和为兼容老项目的场景做知识储备

1.什么是类组件?如何编写一个类组件

定义:通过JS中的类来组织组件的代码
步骤:

  • 1.通过类属性state来定义状态数据
  • 2.通过setState方法来修改状态数据
  • 3.通过render来写UI模板(JSX语法)

示例:

//App.js
import { Component } from 'react'
class Counter extends Component {//基类:Component
  //step1:定义状态变量
  state={
    count:0
  }
  //事件回调
  clickHandler=()=>{
    //step2:通过setState修改状态数据
    this.setState({
      count:this.state.count+1
    })
  }
  //3.UI模板(JSX)
  render(){
    return <button onClick={this.clickHandler} >{this.state.count}</button>
  }
}
*React18中可以把Counter作为子组件插入App中以显示效果
2.类组件的生命周期函数

定义:组件从创建到销毁的各个阶段自动执行的函数
在这里插入图片描述

常用且重要的2个生命周期函数:
      1).componentDidMount:组件挂载完毕时执行,使用场景:获取异步数据
      2).componentWillUnmount:组件卸载时自动执行,使用场景:清理副作用
示例:

//App.js
import { Component } from 'react'
class Counter extends Component {//基类:Component
  //step1:定义状态变量
  state={
    count:0
  }
  //事件回调
  clickHandler=()=>{
    //step2:通过setState修改状态数据
    this.setState({
      count:this.state.count+1
    })
  }
  /*生命周期*/
  //组件渲染完毕时执行一次,发送网络请求
  componentDidMount(){
    console.log('组件渲染完毕了,发送网络请求...')
    this.timer=setInterval(()=>{
      console.log('定时器运行中...')
    },1000)
  }
  //组件卸载时执行,清理副作用,如清除定时器,清除事件绑定
  componentWillUnmount(){
    console.log('组件卸载了,清除副作用...')
    clearInterval(this.timer)
  }
  //3.UI模板(JSX)
  render(){
    return <button onClick={this.clickHandler} >{this.state.count}</button>
  }
}
3.类组件的组件通信

概念:类组件和Hooks编写的组件,在组件通信的设计思路上完全一致

父传子:通过prop绑定数据
子传父:通过prop绑定父组件的函数,子组件调用
兄弟组件通信:状态提升

示例一:父传子:

//App.js
//父组件
class Parent extends Component{
	render() {
   		 return <Child msg="hello from Parent" />;
 	 }
}
//子组件
class Child extends Component{
	render() {
   		 return <div>子组件接收到的数据:{this.props.msg}</div>;
 	 }
}

示例二:子传父

//App.js
//父组件
class Parent extends Component{
  render() {
    const getSonMsg = (msg) => {
      console.log('父组件收到子组件的消息:', msg)
    }
    return <div>
      <div>父组件</div>
      <Child onGetSonMsg={getSonMsg} />
    </div>
  }
}
//子组件
class Child extends Component{
  render() {
    const sendMsg = () => {
      this.props.onGetSonMsg('hello from child')
    }
    return <div>
      <div>子组件</div>
      <button onClick={sendMsg}>点击子传父</button>
    </div>
  }
}

二.zustand - 状态管理工具

官网:https://awesomedevin.github.io/zustand-vue/docs/introduce/start/zustand

1.基础用法

流程:和redux一样:

  • step1.创建store(新增状态数据和操作方法)
  • step2. 绑定到组件
  • step3.在组件中使用状态数据和方法

示例:

//创建store
const useStore=create((set)=>{
	return{
		//状态管理
		count:1,
		//修改状态变量的方法
		inc:()=>{
			set((state)=>({count:state.count+1}))
		}
	}
})

function App(){
	//绑定store到组件
	count {count,inc}=usStore()
	return (
		<button onClick={inc}>{count}</button>
	)
}

export default App
2.异步写法

zustand对异步的支持不需要特殊的操作,
直接在函数中书写异步逻辑,最后只需调用set方法并传入新状态即可

const useStore=create((set)=>{
	return{
		//状态管理
		count:1,
		channelList:[]
		//修改状态变量的方法
		inc:()=>{
			set((state)=>({count:state.count+1}))
		}
		//异步方法
		fetchChannelList:async ()=>{
			const res=await fetch(URL)
			const jsonData=await res.json()
			//调用set方法更新状态变量
			set({
				channelList:jsonData.data.channels
			})
		}
	}
})

function App(){
	//绑定store到组件
	count {count,inc}=useStore()
	count {channelList,fetchChannelList}=useStore()
	return (
		<button onClick={inc}>{count}</button>
		<ul>
			{
				channelList.map(item=><li key={item.id}>{item.name}</li>)
			}
		</ul>
	)
}
3.切片模式

当单个store比较大的时候,可以采用切片模式对模块进行拆分组合,类似于模块化
示例:把上例中的两个store拆分成子模块

//App.js
import {create} from 'zustand'
//1.创建counter相关的切片
const createCounterStore=(set)=>{
	return{
		//状态管理
		count:1,
		//修改状态变量的方法
		inc:()=>{
			set((state)=>({count:state.count+1}))
		}
	}
}

const createChannelStore=(set)=>{
	return{
		//状态管理
		count:1,
		channelList:[]
		//修改状态变量的方法
		inc:()=>{
			set((state)=>({count:state.count+1}))
		}
		//异步方法
		fetchChannelList:async ()=>{
			const res=await fetch(URL)
			const jsonData=await res.json()
			//调用set方法更新状态变量
			set({
				channelList:jsonData.data.channels
			})
		}
	}
}

//3.组合切片
const useStore=create((...a)=>({
	...createCounterStore,
	...createChannelStore
}))


function App(){
	//2.绑定store到组件
	count {count,inc}=useStore()
	count {channelList,fetchChannelList}=useStore()
	return (
		<button onClick={inc}>{count}</button>
		<ul>
			{
				channelList.map(item=><li key={item.id}>{item.name}</li>)
			}
		</ul>
	)
}

export default App

三.React中的Typescript

1.vite介绍

工程化工具:webpack,vite
官网:https://cn.vitejs.dev/guide/

vite是下一代的前端工具链,为开发提供极速响应
vite与框架无关,独立于框架,因此,基于vite可以创建与vue相关的项目,跟原生js的项目,或与react+ts相关的项目

2.创建项目

npm如何创建一个vite+react-ts项目:

npm create vite@latest 项目名 -- template react-ts项目
(该命令同样可以用于创建vue,vue-ts,react项目)
  • 1.安装vite并创建项目:npm create vite@latest my-react-ts-proj -- template react-ts
  • 2.进入项目目录:cd my-react-ts-proj
  • 3.启动项目:npm run dev
  • 4.删除无关目录和文件:
目录:
	assets
文件:
	App.css
	index.css
  • 5.删除一些文件中的无关配置
	//App.jsx
	function App(){
		return <>this is app </>
	}
	export default App

	//main.js
	//删掉index.css的引入
	//严格节点会让代码执行两次,应该注释掉
  //<React.StrictMode>
    //<Provider store={store}><App /></Provider>
  //</React.StrictMode>
  //最终写法:
  import ReactDom from 'react-dom/client'
  import App from './App.tsx'
  ReactDom.createRoot(document.getElementById('root')!).render(<App/>)
3.useState和TS

useState和TS有两种方式进行配合:

自动推导

通常react会根据useState的默认值来自动推导类型,不需要显式标注类型:const [toggle,setToggle]=useState(false)

传递泛型函数

useState本身是一个泛型函数,可以传入具体类型的自定义类型

type User={
	name:string,
	age:number
}
const [user,setUser]=useState<User>()
什么时候把useState的初始值设置为null?

当不确定状态的初始值时,可以将其设置为null,此时类型要改成:const [user,setUser]=useState<User | null>(null)

4.props和TS
4.1.基础使用 - 为props添加类型

为组件props添加类型,本质上就是给函数参数做类型注解,可以使用type对象类型或interface接口来做注解

//App.js
//添加类型
type Props={
	className:string
}
//Button组件
function Button(props:Props){
	const {className}=props
	return <Button className={className}>点击</Button>
}
/*注:Button组件只能传入一个名为className,;类型为string的props参数(必填)*/

function App(){
	return {
		<>
			<Button className="test"/>
		</>
	}
}
export default App

4.2.特殊的props – 为children添加类型

children属性也是特殊的props,
特殊之处在于:children会作为组件标签插入内容(类似插槽),支持多种不同类型的数据传入,需要通过一个内置的ReactNode类型来做注解

//App.js
//添加类型
type Props={
	className:string
	children:React.ReactNode
}
//Button组件
function Button(props:Props){
	const {className,children}=props
	return <Button className={className}>{children}</Button>
}
/*注:Button组件只能传入一个名为className,;类型为string的props参数(必填)*/

function App(){
	return {
		<>
			<Button className="test">点击</Button>
			{/*span标签也是ReactNode支持的类型*/}
			<Button className="test"><span>hello</span></Button>
		</>
	}
}
export default App
4.3.为事件中的props添加类型

在子传父时,组件经常执行类型为函数的props来实现子传父,这类props的重点在于函数参数的类型注解

//App.js
//定义一个Props类型
type Props={
	onGetMsg?:(msg:string)=>void
}
//子组件
function Son(props:Props){
	const {onGetMsg}=props
	const clickHandle=()=>{
		onGetMsg?.("hello ts!")
	}
	return <Button onClick={clickHandle}>发送数据</Button>
}
//父组件
function App(){
	const getMsgHandle=(msg:string)=>{
		console.log(msg)
	}
	return {
		<>
			<Son onGetMsg={getMsgHandle} />
		</>
	}
}
export default App	

*注:

  • 在组件内部调用时序遵守类型约束,传递的参数要满足要求(即:msg必须是string)
  • 绑定的props:
    若绑定内联函数直接可以推断出参数类型
    否则需要单独注解匹配参数的类型
情况一:绑定内联函数--直接可以推断出参数类型
  <>
    <Son onGetMsg={msg=>console.log(msg)} />
  </>	
  
情况二:函数单独写--单独注解匹配参数的类型
  <>
    <Son onGetMsg={getMsgHandle} />
  </>
//类型推断丢失,手动把msg:string补上去
const getMsgHandle=(msg:string)=>{
  console.log(msg)
}

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

5.使用useRef获取DOM时添加类型推断
//示例:获取DOM(input框)
function App(){
	//HTMLInputElement是内置类型
	const domRef=useRef<HTMLInputElement>(null)
	useEffect(()=>{
		//可以自动推断出current属性的类型
		domRef.current?.focus()
	},[])
	return (
		<>
			<input type="text" ref={domRef}
		</>
	)
}
6.把useRef当成引用稳定的存储器来使用的场景下,添加类型推断
function App(){
	const timerRef=useRef<number | undefined>(undefined)
	useEffect(()=>{
		timerRef.current=setInterval(()=>{
			console.log('1111')
		},1000)
		return ()=>clearInterval(timerRef.current)
	},[])
}	

知识点:

处理定时器时最好使用 useRef 而不是普通变量
普通变量在每次渲染时都会重新创建定时器 ID 
需要在组件重新渲染时保持不变,以便正确清除
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

前端OnTheRun

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值