React18速成–适合有vue基础的同学
目录
前言
该博客为本人的学习记录,并不是一篇教程,请结合b站视频食用:
【30分钟学会React18核心语法 可能是你学会React最好的机会 前端开发必会框架 无废话精品视频】https://www.bilibili.com/video/BV1pF411m7wV?vd_source=0d0d6b12377aa593bc3a34f0884de98a
有vue基础的同学建议看完视频入门后去看官方文档学习
若是没有vue基础的同学可以去看长教学视频学习(本视频可看可不看)
ps:代码看不懂的话可以下载vscode插件通义千问等AI插件,一键注释
脚手架创建react项目
npx create-react-app 项目名字
jsx重点知识
1.在jsx语法中html直接内嵌在函数中并通过return返回
2.jsx只能有一个根元素(整合成一个盒子)
// @ts-nocheck
import logo from './logo.svg';
import './App.css';
function App() {
return (
<>
<div className="App">
<header className="App-header">
<img src={logo} className="App-logo" alt="logo" />
</header>
</div>
<div></div>
</>
);
}
export default App;
3.插值
条件渲染
function App(){
const divTitle = '标签标题'
const flag = true;
let divContent = ''
if(flag){
divContent = <span>flag为true</span>//不需要为字符串形式
}else{
divContent = <p>flage为false</p>
}
return(
<div title={divTitle}>{divContent}</div>
)
}
export default App;
列表渲染
function App(){
const list = [
{id:1,name:'xiaowu'},
{id:2,name:'xiaoli'},
{id:3,name:'xiaohua'}
]
const listContent = list.map(item => (
<li key={item.id}>{item.name}</li>
))
return(
<ul>{listContent}</ul>
)
}
export default App;
4.事件操作
基础操作
import React, { Fragment, useState } from "react";
function App(){
// 读 改 (名字可以自己定义)
const [content, setContent] = useState('默认内容')
function handleClick(){
setContent('新内容')
}
return(
<>
<div>{content}</div>
<button onClick={handleClick}>按钮</button>
</>
)
}
export default App;
对象操作
import React, { Fragment, useState } from "react";
function App(){
const [data, setData] = useState({
title:'默认标题',
content:'默认内容'
})
function handleClick(){
setData({
...data,
title:'新标题'
})
}
return(
<>
<div title={data.title}>{data.content}</div>
<button onClick={handleClick}>按钮</button>
</>
)
}
export default App;
数组操作
import React, { Fragment, useState } from "react";
function App(){
const [data, setData] = useState([
{id:1,name:'xiaowu'},
{id:2,name:'xiaoli'},
{id:3,name:'xiaohua'}
])
const listData = data.map(item=>(
<li key={item.id}>{item.name}</li>
))
let td=3;
function handleClick(){
setData([
...data,
{id:++td,name:'xiaohuamao'}
])
}
return(
<>
<ul>{listData}</ul>
<button onClick={handleClick}>按钮</button>
</>
)
}
export default App;
React18组件通信与插槽
1.为dom组件设置Props
在react中给dom设置class时防止与js中class重复:采用className
// @ts-nocheck
import image from './logo.svg'
function App(){
return(
<div>
<img
src={image}
alt=""
className=""
/>
</div>
)
}
export default App;
以变量形式赋值style
// @ts-nocheck
import image from './logo.svg'
function App(){
const imgStyleObj={
with:100,
height:100,
backgroundColor:'grep'
}
return(
<div>
<img
src={image}
alt=""
className=""
style={imgStyleObj}
/>
</div>
)
}
export default App;
通过jsx展开语法(与es6中展开运算符不同,不要混淆)
// @ts-nocheck
import image from './logo.svg'
function App(){
const imgStyleObj={
className:'small',
with:100,
height:100,
backgroundColor:'grep'
}
return(
<div>
<img
src={image}
alt=""
{...imgStyleObj}
/>
</div>
)
}
export default App;
2.react组件的Props
react简单函数式组件
function Article({title,content,active}){
return(
<div>
<h2>{title}</h2>
<p>{content}</p>
<p>状态:{active ? '显示中' : '已隐藏'}</p>
</div>
)
}
export default function App() {
return(
<>
<Article
title="1"
content="1"
active
/>
</>
)
}
嵌套式传值
function Detail ({content,active}){
return(
<>
<p>{content}</p>
<p>状态:{active ? '显示中' : '已隐藏'}</p>
</>
)
}
function Article({title,detailData}){
return(
<div>
<h2>{title}</h2>
<Detail {...detailData}/>
</div>
)
}
export default function App() {
const ArticleData = {
title:'标题1',
detailData:{
content:'内容1',
active:true
}
}
return(
<>
<Article
{...ArticleData}
/>
</>
)
}
react中插槽传值
function List ({children}) {
return (
<ul>
{children}
</ul>
)
}
export default function App() {
return(
<>
<list>
<li></li>
<li></li>
<li></li>
</list>
</>
)
}
react父子传值
// @ts-nocheck
import {useState} from "react"
function Detail({onActive}){
const [status,setStatus] = useState(false)
function handleClick(){
setStatus(!status)
onActive(status)
}
return (
<div>
<button onClick={handleClick}>按钮</button>
<p style={{
display:status ? 'block' : 'none'
}}>Detail的内容</p>
</div>
)
}
export default function App() {
function handleActive(status){
console.log(status)
}
return(
<>
<Detail
onActive={handleActive}
/>
</>
)
}
React Hooks
1.reducer
// @ts-nocheck
import {useReducer, useState} from "react"
function countReducer(state,action){
switch(action.type){
case "increment":
return state + 1
case "decrement:":
return state - 1
default:
throw new Error()
}
}
export default function App() {
const [state,dispatch] = useReducer(countReducer,0)
const handleIncrement = () => dispatch({type:"increment"})
const handleDecrement = () => dispatch({type:"decrement"})
return(
<div style={{padding:10}}>
<button onClick={handleIncrement}>-</button>
<span>{count}</span>
<button onClick={handleDecrement}>+</button>
</div>
)
}
2.useRef
—记录修改前的状态
记录上一次的值
// @ts-nocheck
import {useReducer, useState} from "react"
export default function App() {
const [count,setCount] = useState(0)
const prevCount = useRef()
function handleClick(){
prevCount.current = count
setCount(count + 1)
}
return(
<div>
<p>最新的count:{count}</p>
<p>上次的count:{prevCount.current}</p>
<button onClick={handleClick}>增大count</button>
</div>
)
}
通过ref实现子组件功能访问
// @ts-nocheck
import { forwardRef, useImperativeHandle, useReducer, useState } from "react"
/**
* 定义一个名为Child的组件,使用forwardRef创建一个引用。
* 这个组件通过引用来提供一个自定义的focus方法。
*
* @param {Object} props - 组件的props,不包含特定的属性。
* @param {Object} ref - 一个用于获取组件实例的引用。
* @returns 返回一个简单的div元素,这个div通过ref暴露了focus方法。
*/
const Child = forwardRef((props, ref) => {
// 使用useImperativeHandle钩子来定义ref的行为。
useImperativeHandle(ref, () => ({
focus: () => {
console.log('focus')
}
}))
return (
<div>子组件</div>
)
})
/**
* App组件是应用程序的主入口点。
* 它使用了一个ref来直接访问Child组件的实例,并且提供了一个按钮来触发Child组件的focus方法。
*
* @returns 返回一个包含Child组件和一个按钮的div元素。
*/
export default function App() {
// 创建一个ref来保存Child组件的实例。
const childRef = useRef()
/**
* 点击按钮时调用的函数,用于触发Child组件的focus方法。
*/
function handleClick() {
childRef.current.focus()
}
return (
<div>
<Child ref={childRef} />
<button onClick={handleClick}>聚焦</button>
</div>
)
}
3.useEffect
// @ts-nocheck
import { useState,useEffect } from "react"
export default function App() {
const [count, setCount] = useState(0)
const handleIncrement = () => {
setCount(count + 1)
}
const handleDecrement = () => {
setCount(count - 1)
}
useEffect(() => {
console.log('useEffect')
},[count]);
return (
<div style={{padding:10}}>
<button onClick={handleIncrement}>-</button>
<span>{count}</span>
<button onClick={handleDecrement}>+</button>
</div>
)
}
4.useMemo
如何使用useMemo
- 确定计算结果: 首先,识别出你的组件中哪些计算是昂贵的,并且在每次渲染时都不需要重新计算,除非某些特定的状态或属性发生了改变。
- 创建记忆化的值: 使用
useMemo
来包装这个计算,将计算逻辑放在useMemo
的第一个参数的函数中,这个函数被称为工厂函数。 - 指定依赖项: 在
useMemo
的第二个参数中,提供一个依赖项数组。这个数组应该包含所有影响计算结果的变量。如果依赖项数组中的任何值发生变化,useMemo
将重新执行工厂函数。 - 使用记忆化的值: 在组件的其他部分使用
useMemo
返回的记忆化值,而不是直接调用计算函数。
假设你有一个组件,它需要根据用户的输入动态生成一个复杂的图表数据集。你可以使用useMemo
来避免在每次渲染时都重新生成这个数据集。
import React, { useState, useMemo } from 'react';
function ChartDataGenerator() {
const [inputValue, setInputValue] = useState(0);
const complexChartData = useMemo(() => {
// 这个函数模拟一个复杂的计算过程
const data = Array.from({ length: inputValue }, (_, i) => Math.random());
return data;
}, [inputValue]);
return (
<div>
<input type="number" value={inputValue} onChange={e => setInputValue(Number(e.target.value))} />
<pre>{JSON.stringify(complexChartData, null, 2)}</pre>
</div>
);
}
在这个例子中,complexChartData
只有在inputValue
改变时才会重新计算。这可以显著提高组件的性能,特别是在complexChartData
的生成是一个昂贵的操作时。
注意事项
useMemo
主要用于避免不必要的计算,但它不应该被过度使用。在大多数情况下,React的虚拟DOM算法已经足够高效,不需要额外的优化。- 如果依赖项数组为空,
useMemo
将只在组件挂载时计算一次。 - 如果依赖项数组中的某个值是引用类型(如对象或数组),即使其内部状态没有改变,只要引用本身改变了,
useMemo
也会重新计算。 - 不要在
useMemo
的依赖项数组中包含函数或类实例,因为它们在每次渲染时都会创建新的引用,这会导致useMemo
每次都重新执行。
5.useCallBack
与useMemo很像
区别:useCallBack作用于变量,useMemo作用于函数
一个例子说明区别:
import React, { useState, useCallback, useMemo } from 'react';
function CounterApp() {
const [counter, setCounter] = useState(0);
// 使用 useCallback 缓存 函数 以避免在每次渲染时创建新的函数引用
const incrementCounter = useCallback(() => {
setCounter(counter + 1);
}, [counter]);
// 使用 useMemo 缓存计算结果( 变量 ),避免在每次渲染时重复计算
const fibonacci = useMemo(() => {
if (counter <= 1) return counter;
return fibonacci(counter - 1) + fibonacci(counter - 2);
}, [counter]);
return (
<div>
<h1>Counter: {counter}</h1>
<button onClick={incrementCounter}>Increment</button>
<p>Fibonacci of {counter}: {fibonacci}</p>
</div>
);
}
export default CounterApp;
-
useCallback 示例:
在上面的例子中,
incrementCounter
函数使用useCallback
进行缓存。这意味着只要counter
的值没有改变,incrementCounter
函数的引用就不会改变。这对于性能优化很重要,特别是在将函数作为 prop 传递给子组件时,可以避免不必要的子组件重新渲染。 -
useMemo 示例:
fibonacci
计算使用了useMemo
。由于斐波那契数列的计算可能是昂贵的操作,我们不想在每次组件渲染时都重新计算它,除非counter
的值确实改变了。useMemo
确保了只有当依赖项counter
发生变化时,计算才被执行,否则它将返回上一次计算的结果。
总结
useCallback
用于优化函数引用,避免在每次渲染时创建新的函数实例,这有助于减少不必要的子组件重新渲染。useMemo
用于优化计算结果的缓存,避免在每次渲染时重复执行相同的计算,这有助于减少不必要的计算开销。
在上述示例中,useCallback
和 useMemo
分别用于优化事件处理器和计算密集型操作,共同提高了应用的性能。
TodoList综合案例react18+ts
page.tsx
import AddTodo from '../components/AddTodo'
import TodoList from '../components/TodoList'
import TodoFilter from '@/components/TodoFilter'
import { useState } from 'react';
import { Todo } from '@/type';
export default function Home() {
const [todos, setTodos] = useState<Todo[]>([]);
const [filter, setFilter] = useState('all');
//添加功能
const addTodo = (text:string) => {
const newTodo = {
id:Date.now(),
text,//在这
completed:false
}
setTodos([...todos,newTodo])
}
//删除功能
const deleteTodo = (id:number) => {
setTodos(todos.filter(todos => todos.id !== id))
}
//切换功能
const toggleTodo = (id:number) => {
setTodos(todos.map(todo => {
if(todo.id === id){
return {
...todo,
completed:!todo.completed
}
}
return todo
}))
}
//过滤功能
const getFilteredTodos = () =>{
switch(filter){
case 'all':
return todos
case 'completed':
return todos.filter(todo => todo.completed)
case 'active':
return todos.filter(todo => !todo.completed)
default:
return todos
}
}
return (
<div>
<h1>TodoList</h1>
<AddTodo addTodo={addTodo}></AddTodo>
<TodoList todos={getFilteredTodos()} deleteTodo={deleteTodo} toggleTodo={toggleTodo}></TodoList>
<TodoFilter setFilter={setFilter}></TodoFilter>
</div>
)
}
type.ts
export interface Todo{
id:number;
text:string;
completed:boolean;
}
AddTodo.tsx
import { useState } from "react"
interface AddTodoProps{
addTodo: (text:string) => void
}
function AddTodo({addTodo}:AddTodoProps){
const [text,setText] = useState('')
const handleSubmit = (e:React.FormEvent<HTMLFormElement>) => {
e.preventDefault()
if(text.trim() === ''){
return
}
addTodo(text)
setText('')
}
return (
<form>
<input
type="text"
value={text}
onChange={e => setText(e.target.value)}
/>
<button>新建事项</button>
</form>
)
}
export default AddTodo
TodoItem.tsx
import { Todo } from "@/type"
interface TodoItemProps {
todo:Todo;
toggleTodo: (id:number) => void;
deleteTodo: (id:number) => void;
}
function TodoItem({todo,toggleTodo,deleteTodo}:TodoItemProps){
return(
<li style={{textDecoration:todo.completed ? 'line-through' : 'none'}}>
{todo.text}
<button onClick={() => toggleTodo(todo.id)}>切换</button>
<button onClick={() => deleteTodo(todo.id)}>删除</button>
</li>
)
}
export default TodoItem
TodoFilter.tsx
function TodoFilter ({setFilter}:any){
return(
<div>
<button onClick={() => setFilter('all')}>All</button>
<button onClick={() => setFilter('active')}>Active</button>
<button onClick={()=>setFilter('completed')}>Completed</button>
</div>
)
}
export default TodoFilter
TodoList.tsx
import { Todo } from "../type";
import TodoItem from "./TodoItem";
interface TodoListProps {
todos:Todo[];
toggleTodo: (id:number) => void;
deleteTodo: (id:number) => void;
}
function TodoList({todos,toggleTodo,deleteTodo}:TodoListProps) {
return (
<ul>
{
todos.map(todo => (
<TodoItem key={todo.id} todo={todo} toggleTodo={toggleTodo} deleteTodo={deleteTodo}></TodoItem>
))
}
</ul>
);
}
export default TodoList