教程参考 #
React核心语法 #
搭建项目 #
构建命令 #
使用官方的命令创建
npx create-next-app@latest
使用Vite创建项目
pnpm create vite
创建第一个React项目 #
这里使用npx create-next-app@latest来创建项目
使用pnpm dev启动项目
浏览器访问http://localhost:3000出现以下页面说明搭建完成
TSX(JSX) #
TSX(JSX)将typescript(javascript)语法和html结合在一起的一种语法,是一种模板
删除无用代码方便后续学习 #
修改一下page.tsx文件和layout.tsx文件,方便后续学习
page.tsx文件
export default function Home() {
return (
<div>hello react</div>
)
}
layout.tsx文件
删除全局样式的引入
刷新页面
出现hello react
函数组件 #
一个函数为一个组件,函数的返回值为要渲染的内容
export default function Home() {
return (
<div>hello react</div>
)
}
如果返回的内容只有一行,可以省略 ()
export default function Home() {
return <div>hello react</div>
}
如果返回的内容有多行,括号不能省略
export default function Home() {
return (
<div>
<div>hello</div>
<div>react</div>
</div>
)
}
TXS只能返回一个根元素 #
有点像vue2中的template,只能有一个根元素
出现两个根元素
export default function Home() {
return (
<div>hello react</div>
<div>hello react</div>
)
}
vscode报错提示
TXS空标签 #
<> 多级元素内容 </>
用于处理多级元素
export default function Home() {
return (
// 空标签
<>
<div>hello react</div>
<div>hello react</div>
</>
)
}
最终渲染出来的结构没有产生多余的html元素
TSX展开语法 #
"use client"
export default function Home() {
// 定义一个属性
const objData = {
style: {
color: "red",
// 带有 - 要使用驼峰
fontSize: '30px'
},
className: 'redText',
}
return (
<>
{/* 展开 */}
<div {...objData}>hello world</div>
</>
)
}
数据渲染 #
插值语法 #
看完视频后,感觉有点类似vue的插值语法,哈哈哈
标签内容 #
export default function Home() {
const title = 'hello react'
return (
<>
{/* 使用在标签内部 */}
<div>{title}</div>
</>
)
}
标签属性 #
export default function Home() {
const title = 'hello react'
// 标签属性
const titleAttribute = 'titleAttribute'
return (
<>
{/* 使用标签属性 */}
<div title={titleAttribute}>{title}</div>
</>
)
}
条件渲染 #
使用三元表达式
export default function Home() {
let flag = false
return (
<>
<div>{flag?'hello react':'hello vue'}</div>
</>
)
}
在函数中使用条件判断
export default function Home() {
let title = null
let flag = true
// 定义一个flag标志
if(flag){
title = <span>hello react</span>
} else {
title = <span>hello vue</span>
}
return (
<>
<div>{title}</div>
</>
)
}
flag为true
flag为false
列表渲染 #
先写的todolist再回过头来看基本语法,map在react中用的应该是非常多
列表中的key用于保证元素的唯一性,类似vue的v-for中的key
export default function Home() {
const list = ['吃饭', '睡觉', '看手机', '打游戏']
return (
<>
<ul>
{list.map((item) => <li key={item}>{item}</li>)}
</ul>
</>
)
}
使用Fragment来包裹返回的多个根元素
和<></>类似,Fragment里可以写key,但<></>不行
import { Fragment } from "react"
export default function Home() {
const list = ['吃饭', '睡觉', '看手机', '打游戏']
return (
<>
<ul>
{list.map((item) => (
// 使用Fragment包裹多级元素
<Fragment key={item}>
<li >{item}</li>
<li>----</li>
</Fragment>
))}
</ul>
</>
)
}
事件处理 #
标签的属性通常使用驼峰
点击事件:onClick
"use client"
export default function Home() {
const handleClick = () => {
alert('click')
}
return (
<>
<button onClick={handleClick}>按钮</button>
</>
)
}
状态处理 #
useState #
const [state, setState] = useState(initialState)
initialState为初始值
state为数据
setState用于修改state的函数
"use client"
// 导入useState
import { useState } from "react"
export default function Home() {
// 使用useState
const [title,setTitle] = useState<string>('标题x')
const handleClick = () => {
setTitle('标题a')
}
return (
<>
<h1>{title}</h1>
<button onClick={handleClick}>按钮</button>
</>
)
}
点击按钮修改title
修改对象的技巧 #
先展开旧的对象,再覆盖旧的属性值
"use client"
import { useState } from "react"
interface Content {
title: string;
name: string;
}
export default function Home() {
const [content, setContent] = useState<Content>({
title: '标题1',
name: '吃饭'
})
const handleClick = () => {
// 修改对象的某个属性
setContent({
// 先展开对象
...content,
// 新的属性值会覆盖旧的属性值
name: '睡觉'
})
}
return (
<>
<h1>{content.name}</h1>
<button onClick={handleClick}>按钮</button>
</>
)
}
点击按钮
修改数组的技巧 #
vue中可以使用push,pop直接修改数组,react中有点小麻烦
先展开数组,再添加一个新的元素
"use client"
import { useState } from "react"
export default function Home() {
const [list, setList] = useState([1, 2, 3])
const handleClick = () => {
// 先展开数组,再添加一个新的元素
setList([...list, 4])
}
return (
<>
<ul>
{list.map((item) => <li key={item}>{item}</li>)}
</ul>
<button onClick={handleClick}>按钮</button>
</>
)
}
点击按钮新增一个数组元素4
React组件通信与插槽 #
ReactDom组件 #
设置props属性 #
"use client"
export default function Home() {
const url = 'https://bing-wu-doc.netlify.app/background.svg'
return (
<>
<div>hello world</div>
{/* 在html元素上使用props */}
<img src={url} alt="" />
</>
)
}
设置className属性 #
"use client"
export default function Home() {
return (
<>
<div className="redText">hello world</div>
</>
)
}
设置style属性 #
带有 - 要使用驼峰
"use client"
export default function Home() {
// 设置样式
const titleStyle = {
color: "red",
// 带有 - 要使用驼峰
fontSize: '30px'
}
return (
<>
{/* style */}
<div style={titleStyle}>hello world</div>
</>
)
}
React组件 #
创建一个React组件 #
export default function MyApp() {
return (
<div>myapp组件</div>
)
}
导入react组件并使用
设置props属性 #
父传子 #
MyApp组件
interface MyAppProps {
content: string;
sayHello: () => void;
active: boolean;
}
export default function MyApp({ content, sayHello, active }: MyAppProps) {
return (
<>
<div>myapp组件</div>
<div>{content}</div>
<button onClick={sayHello}>按钮</button>
<div>{active ? '激活' : '未激活'}</div>
<div>------</div>
</>
)
}
pages组件
active未设置值,默认为true
"use client"
import MyApp from "./MyApp"
export default function Home() {
const sayHello = ()=>{
alert('hello')
}
return (
<>
<div>hello world</div>
<MyApp content={'内容1'} sayHello={sayHello} active></MyApp>
</>
)
}
父传子再传孙 #
page组件
"use client"
import MyApp from "./MyApp"
export default function Home() {
const data = {
content: "我要吃饭",
childData: {
title: '今天天气好',
date: "2077-9-1"
}
}
return (
<>
<div>hello world</div>
{/* 展开data */}
<MyApp {...data}></MyApp>
</>
)
}
MyApp组件
import MyChild from "./MyChild";
interface MyAppProps {
content: string;
childData: {
title: string;
date: string;
}
}
export default function MyApp({ content, childData }: MyAppProps) {
return (
<>
<div>myapp组件</div>
<div>{content}</div>
{/* 展开要传入到孙组件的数据 */}
<MyChild {...childData}></MyChild>
</>
)
}
MyChild组件
创建一个MyChild组件
填入以下代码
interface MyChildProps {
title: string;
date: string;
}
export default function MyChild({ title, date }: MyChildProps) {
return (
<>
<div>mychild组件</div>
<div>{title}</div>
<div>{date}</div>
</>
)
}
页面中展示的效果
插槽 #
普通传参 #
myapp组件
// 使用默认的属性children获取传入过来的html元素
export default function MyApp({ children }: any) {
return (
<>
<div>myapp组件</div>
<div>
{/* 使用 */}
{children}
</div>
</>
)
}
page组件
"use client"
import MyApp from "./MyApp"
export default function Home() {
return (
<>
<div>hello world</div>
<MyApp>
{/* 往myapp组件传入的html元素 */}
<div>1</div>
<div>1</div>
<div>1</div>
</MyApp>
</>
)
}
向多个位置传递 #
page组件
"use client"
import MyApp from "./MyApp"
export default function Home() {
return (
<>
<div>hello world</div>
<MyApp content={'吃饭吃饭吃饭'} title={'天气好好'}>
{/* 往myapp组件传入的html元素 */}
<div>1</div>
<div>1</div>
<div>1</div>
</MyApp>
{/* 不传title */}
<MyApp content={'睡觉睡觉睡觉'}>
<div>1</div>
<div>2</div>
<div>3</div>
</MyApp>
</>
)
}
myapp组件
// props的属性设置默认值表示可选的
// title可选
export default function MyApp({ children, content, title = '默认标题' }: any) {
return (
<>
<div>myapp组件</div>
<div>
{/* 使用 */}
{children}
</div>
<div>{title}</div>
<div>{content}</div>
</>
)
}
子传父 #
有点像vue的emits
page组件
"use client"
import MyApp from "./MyApp"
export default function Home() {
// 传入到myapp组件的函数
const getData = (data: string) => {
console.log('data', data);
}
return (
<>
<div>hello world</div>
{/* 传入函数 */}
<MyApp getData={getData}>
</MyApp>
</>
)
}
myapp组件
import { useState } from "react"
export default function MyApp({ getData }: any) {
const [name] = useState('小李')
return (
<>
<div>myapp组件</div>
<div>{name}</div>
{/* myapp组件触发page组件传入过来的函数 */}
<button onClick={(e) => getData(name)}>按钮</button>
</>
)
}
获取到myapp组件的数据
多层组件传参 #
组件嵌套层数很多,4-5层以上的时候
如果是多文件组件,感觉不出来有什么用,待考量
方式一 #
"use client"
// 导入
import { createContext, useContext } from "react"
// 创建一个context
const DataContext = createContext('今天吃饭了吗')
function MyChild() {
// 使用context
const data = useContext(DataContext)
return (
<>
<div>my-child</div>
<div>{data}</div>
</>
)
}
export default function Home() {
return (
<>
<div>hello world</div>
<MyChild></MyChild>
</>
)
}
方式二 #
使用Consumer和Provider
"use client"
import { createContext, useState } from "react"
// 创建一个context
const DataContext = createContext('')
function MyChild() {
return (
<>
<div>MyChild组件</div>
<DataContext.Consumer>
{/* 使用插值语法 */}
{
// 包裹一个函数 函数第一个参数是Provider传入过来的数据
(value) => <div>{value}</div>
}
</DataContext.Consumer>
</>
)
}
export default function Home() {
const [data] = useState<string>('今天吃了面')
return (
<>
<div>hello world</div>
{/* 要传递过去的数据 */}
<DataContext.Provider value={data}>
<MyChild> </MyChild>
</DataContext.Provider>
</>
)
}
ReactHooks速成 #
useReducer #
统一管理状态的操作方式
"use client"
import { useReducer } from "react"
interface action {
type: string
}
// 创建一个函数
const countReducer = (state: number, { type }: action) => {
if (type === 'increment') {
return state + 1
} else if (type === 'decrement') {
return state - 1
} else {
return state
}
}
export default function Home() {
// 第一个参数为更新state的函数,第二参数为state的初始值
const [count, dispatch] = useReducer(countReducer, 0)
return (
<>
<div>hello world</div>
<div>{count}</div>
<button onClick={e => dispatch({ type: 'increment' })}>+</button>
<button onClick={e => dispatch({ type: 'decrement' })}>-</button>
</>
)
}
useRef #
用于记录上次状态更新的值 #
"use client"
import { useRef,useState } from "react"
export default function Home() {
const [count, setCount] = useState(0)
// 参数为默认值
const preCount = useRef(-1)
const handleClick = () => {
preCount.current = count
setCount(count + 1)
}
return (
<>
<div>hello world</div>
<div>当前的count{count}</div>
<div>上一次的count{preCount.current}</div>
<button onClick={handleClick}>count++</button>
</>
)
}
用于获取dom对象 #
获取普通的html元素
"use client"
import { useRef} from "react"
export default function Home() {
const divRef = useRef(null)
const handleClick = () => {
console.log('dom',divRef.current.innerHTML);
}
return (
<>
<div>hello world</div>
<div ref={divRef}>div标签</div>
<button onClick={handleClick}>get</button>
</>
)
}
forwardRef&useImperativeHandle #
forwardRef
允许组件使用 ref 将 DOM 节点暴露给父组件。
useImperativeHandle
是 React 中的一个 Hook,它能让你自定义由 ref 暴露出来的句柄。
"use client"
import { useRef, forwardRef, useImperativeHandle } from "react"
// 要暴露出去的组件
const MyChild = forwardRef((props, ref) => {
useImperativeHandle(ref, () => {
return {
// 要暴露出去的方法
showHello() {
console.log('hello world')
}
}
})
return <div>mychild组件</div>
})
// 设置显示名称
MyChild.displayName = 'MyChild';
export default function Home() {
const myChildRef = useRef(null)
const handleClick = () => {
myChildRef.current.showHello()
}
return (
<>
<div>hello world</div>
<MyChild ref={myChildRef}></MyChild>
<button onClick={handleClick}>get</button>
</>
)
}
输出hello world
useEffect #
类似vue中的watch
依赖的数据发生变化,useEffect函数就会执行
"use client"
import { useEffect, useState } from "react"
export default function Home() {
const [num, setNum] = useState(0)
// num为要依赖的数据
useEffect(() => {
console.log("useEffect")
}, [num])
return (
<>
<div>hello world</div>
{/* num变化,执行useEffect */}
<button onClick={() => setNum(num + 1)}>++</button>
</>
)
}
useMemo #
父组件的重新渲染会引起子组件的重新渲染
"use client"
import { useState } from "react"
const MyChild = () => {
console.log("MyChild组件渲染了")
return (
<div>MyChild组件</div>
)
}
export default function Home() {
const [num, setNum] = useState(0)
return (
<>
<div>hello world</div>
<MyChild />
<button onClick={() => setNum(num + 1)}>++</button>
</>
)
}
只有name变化才会重新渲染子组件(!!!)
"use client"
import { useState, useMemo } from "react"
// 子组件获取父组件传递过来的name
const MyChild = ({ name }) => {
// 让name变更的时候重新渲染子组件
const memoName = useMemo(() => {
console.log("MyChild组件渲染了")
const newName = name + '123'
return newName
}, [name])
return (
<>
<div>MyChild组件</div>
<div>{name}</div>
<div>{memoName}</div>
</>
)
}
export default function Home() {
const [num, setNum] = useState(0)
const [name, setName] = useState("")
return (
<>
<div>hello world</div>
<MyChild name={name} />
<div>{num}</div>
<button onClick={() => setNum(num + 1)}>++</button>
<button onClick={() => setName("name" + name)}>name</button>
</>
)
}