react基础
约 7799 字大约 26 分钟
2025-05-02
1.脚手架创建
1.0 组成部分
- react 核心语法 react
- react 操作节点库
react-dom
- react 跨段原生开发
react navtie
1.1命令
npx create-next-app@latest 文件名
npm create vite@latest 文件名 -- --template react
sacc命令: yarn add -D sass-embedded
项目目录

生命周期
- 初始化周期 init
- 挂载周期
- component
- 更新周期
- 卸载周期
2.JSX基础
什么是JSX
概念:JSX是JavaScript和XMl(HTML)的缩写,表示在JS代码中编写HTML模版结构,它是React中构建UI的方式
const message = 'this is message'
function App(){
return (
<div>
<h1>this is title</h1>
{message}
</div>
)
}
优势:
- HTML的声明式模版写法
- JavaScript的可编程能力
1.1JSX高频场景-JS表达式
在JSX中可以通过
大括号语法{}
识别JavaScript中的表达式,比如常见的变量、函数调用、方法调用等等
- 使用引号传递字符串
- 使用JS变量
- 函数调用和方法调用
- 使用JavaScript对象
注意
注意:if语句、switch语句、变量声明不属于表达式,不能出现在{}中
const message = 'this is message'
function getAge(){
return 18
}
function App(){
return (
<div>
<h1>this is title</h1>
{/* 字符串识别 */}
{'this is str'}
{/* 变量识别 */}
{message}
{/* 变量识别 */}
{message}
{/* 函数调用 渲染为函数的返回值 */}
{getAge()}
{/* 样式书写 */}
<div className="App" style={{color: 'red'}}>
你好{num}
</div>
);
</div>
)
}
1.2map渲染
要加上独一无二的key值 React内部框架使用,提升更新新性能
const list = [
{id:1001, name:'Vue'},
{id:1002, name: 'React'},
{id:1003, name: 'Angular'}
]
function App() {
return (
<div className="App" style={{color: 'red'}}>
{/* 你好 */}
<ul>
{list.map(item => <li key={item.id}>{item.name}</li>)}
</ul>
</div>
);
}
export default App;
1.3jsx条件渲染
在React中,可以通过逻辑与运算符&&、三元表达式(?😃 实现基础的条件渲染
function App() {
return (
<div className="App" style={{color: 'red'}}>
{/* 逻辑与 && */}
{true && <span>你好</span>}
{/* 三元运算 */}
{false ? <span>true</span> :<span>false</span>}
</div>
);
}
export default App;
1.4JSX高频场景-复杂条件渲染
需求:列表中需要根据文章的状态适配 解决方案:自定义函数 + 判断语句
const num = 2
const getImg = () => {
if(num === 1) return <span>单图</span>
if(num === 2) return <span>双图</span>
}
function App() {
return (
<div className="App" style={{color: 'red'}}>
{getImg()}
</div>
);
}
export default App;
3.react事件绑定
on时间 =》 onClick=
const handelClick = () => {
console.log('点击l')
}
function App() {
return (
<div className="App" style={{color: 'red'}}>
<button onClick={handelClick}>点击</button>
</div>
);
}
export default App;
获取时间参数e
const handelClick = (e) => {
console.log('点击l',e)
}
function App() {
return (
<div className="App" style={{color: 'red'}}>
<button onClick={handelClick}>点击</button>
</div>
);
}
export default App;
获取自定义参数
// 传递自定义参数
const handelClick = (name) => {
console.log('点击l',name)
}
function App() {
return (
<div className="App" style={{color: 'red'}}>
<button onClick={() => handelClick('jack')}>点击</button>
</div>
);
}
export default App;
获取自定义参数和时间参数E
const handelClick = (name,e) => {
console.log('点击l',name,e)
}
function App() {
return (
<div className="App" style={{color: 'red'}}>
<button onClick={(e) => handelClick('jack',e)}>点击</button>
</div>
);
}
export default App;
4.React组件基础使用
概念:一个组件就是一个用户界面的一部分,它可以有自己的逻辑和外观,组件之间可以互相嵌套,也可以服用多次
4.1组件基础使用
在React中,一个组件就是首字母大写的函数,内部存放了组件的逻辑和视图UI, 渲染组件只需要把组件当成标签书写即可
const Button = () => {
return(
<button>我是按钮</button>
)
}
function App() {
return (
<div className="App" style={{color: 'red'}}>
<Button />
</div>
);
}
export default App;
外部为文件
const Button = () => {
return (
<button>我是按钮</button>
)
}
export default Button
import Button from "./Button"; // 导入
// const Button = () => {
// return(
// <button>我是按钮</button>
// )
// }
function App() {
return (
<div className="App" style={{color: 'red'}}>
<Button />
</div>
);
}
export default App;
5.组件状态管理-useState[数据驱动视图]
useState 是一个 React Hook(函数),它允许我们向组件添加一个
状态变量
, 从而控制影响组件的渲染结果 和普通JS变量不同的是,状态变量一旦发生变化组件的视图UI也会跟着变化(数据驱动视图)
import { useState } from "react";
function App() {
// 获取状态变量
const [count, setCount] = useState(0)//
const addClick = () => {
// 修改值
setCount(count + 1) // *count只可以用setCount替换*,不可直接修改 count++
}
return (
<div className="App" style={{color: 'red'}}>
<button onClick={addClick}>加1</button> <br></br>
{count}
</div>
);
}
export default App;
修改复杂对象
import { useState } from "react";
function App() {
// 获取状态变量
const [count, setCount] = useState(0)
const [name, setName] = useState({name: '张三'})
const addClick = () => {
// 修改值
setCount(count + 1)
setName({
...name,
name: '李四'
})
}
return (
<div className="App" style={{color: 'red'}}>
<button onClick={addClick}>加1</button> <br></br>
{count}{name.name}
</div>
);
}
export default App;
6.组件的基础样式处理
React组件基础的样式控制有俩种方式,行内样式和class类名控制
<div style={{ color:'red'}}>this is div</div>
.foo{
color: red;
}
import './index.css'
function App(){
return (
<div>
<span className="foo">this is span</span>
</div>
)
}
7.lodash(数组排序工具)
安装
yarn add lodash
import _ from 'lodash' // 导入
const defaultList = [
{
// 评论id
rpid: 3,
// 用户信息
user: {
uid: '13258165',
avatar: '',
uname: '周杰伦',
},
// 评论内容
content: '哎哟,不错哦',
// 评论时间
ctime: '10-18 08:15',
like: 88,
},
{
rpid: 2,
user: {
uid: '36080105',
avatar: '',
uname: '许嵩',
},
content: '我寻你千百度 日出到迟暮',
ctime: '11-13 11:29',
like: 88,
},
{
rpid: 1,
user: {
uid: '30009257',
avatar,
uname: '黑马前端',
},
content: '学前端就来黑马',
ctime: '10-19 09:00',
like: 66,
},
]
// 当前登录用户信息
const user = {
// 用户id
uid: '30009257',
// 用户头像
avatar,
// 用户昵称
uname: '黑马前端',
}
_.orderBy(数组,排序字段,排序规则)
setCommentList(_.orderBy(commentList,'like','desc'))
8.classnames工具化类名控制器
安装
yarn add classnames
import classNames from 'classname' // 导入
className={`nav-item ${tabType === item.type && 'active'}`} // 原来
className={classNames(`nav-item`,{active: type === item.type})} // 改后
受控和非受控组件
非受控组件 通过useRef 获取dom ,通过dom获取数据
受控 通过useState 做数据绑定 通过onChange事件 useCount (e.targe.vulue)赋值
9.受控表单绑定
const [value, setValue] = useState('')
<input
value={value}
onChange={(e) => setValue(e.target.value)}
> </input>
10.React获取DOM【非受控】
const inp = useRef(null)
<input ref={inp} /> // 绑定
console.log(inp.current)// 获取DOM
11.Dayjs时间格式化
安装
yarn add dayjs
导入
import dayjs from 'dayjs'
dayjs().format()
// 默认返回的是 ISO8601 格式字符串 '2020-04-02T08:02:17-05:00'
dayjs('2019-01-25').format('[YYYYescape] YYYY-MM-DDTHH:mm:ssZ[Z]')
// 'YYYYescape 2019-01-25T00:00:00-02:00Z'
dayjs('2019-01-25').format('DD/MM/YYYY') // '25/01/2019'
12.组件通信
父传子
基础实现
**实现步骤 **
- 父组件传递数据 - 在子组件标签上绑定属性
- 子组件接收数据 - 子组件通过props参数接收数据
const Son = (props) => { // props接收 return( <div>我是Son {props.name}</div> ) } const App = () => { const name = '名字' return( // 绑定属性传 <Son name={name}/> ) } export default App
props说明
props可以传递任意的合法数据,比如数字、字符串、布尔值、数组、对象、函数、JSX
props是只读对象子组件只能读取props中的数据,不能直接进行修改, 父组件的数据只能由父组件修改
【插槽】特殊的prop-chilren
场景:当我们把内容嵌套在组件的标签内部时,组件会自动在名为children的prop属性中接收该内容
image.png
React 插槽(Slots)概念及实现方式
在 React 中,"插槽"(Slots)是一种组件设计模式,允许父组件向子组件传递内容或组件,类似于 Vue 中的插槽概念。React 本身没有专门的"插槽"API,但可以通过以下几种方式实现类似功能:
1. 使用 children
属性
传一个 children 就是一个对象
传多个,children 就是一个数组
这是 React 中最基本的插槽实现方式
// 子组件
function Card({ children }) {
return <div className="card">{children}</div>;
}
// 父组件
function App() {
return (
<Card>
<h1>标题</h1>
<p>内容</p>
</Card>
);
}
React 插槽(Slots)概念及实现方式
在 React 中,"插槽"(Slots)是一种组件设计模式,允许父组件向子组件传递内容或组件,类似于 Vue 中的插槽概念。React 本身没有专门的"插槽"API,但可以通过以下几种方式实现类似功能:
1. 使用 children
属性
这是 React 中最基本的插槽实现方式:
jsx
复制
// 子组件
function Card({ children }) {
return <div className="card">{children}</div>;
}
// 父组件
function App() {
return (
<Card>
<h1>标题</h1>
<p>内容</p>
</Card>
);
}
2. 多插槽模式(使用 props)
当需要多个插槽位置时,可以使用 props 传递:
// 子组件
function Layout({ header, content, footer }) {
return (
<div className="layout">
<div className="header">{header}</div>
<div className="content">{content}</div>
<div className="footer">{footer}</div>
</div>
);
}
// 父组件
function App() {
return (
<Layout
header={<h1>网站标题</h1>}
content={<p>主要内容区域</p>}
footer={<small>版权信息</small>}
/>
);
}
3.作用域插槽
// 子组件 Child.jsx
const Child = ({ renderContent }) => {
const data = { user: "Alice", age: 25 };
return <div>{renderContent(data)}</div>;
};
// 父组件 Parent.jsx
const Parent = () => {
return (
<Child
renderContent={({ user, age }) => (
<p>用户名:{user},年龄:{age}</p>
)}
/>
);
};
- 举例
// 子组件 List.jsx
const List = ({ data, renderItem }) => {
return (
<ul>
{data.map((item) => (
<li key={item.id}>{renderItem(item)}</li>
))}
</ul>
);
};
// 父组件 Parent.jsx
const Parent = () => {
const items = [
{ id: 1, name: "Alice" },
{ id: 2, name: "Bob" },
];
return (
<List
data={items}
renderItem={(item) => <span>{item.name}</span>}
/>
);
};
- 子传父亲
主要利用父亲传方法给子组件,子组件使用方法传参
const Son = ({onGetMsg}) => {
// props接收
const str = '我是子信息'
return(
<button onClick={() => onGetMsg(str)}>传信息</button>
)
}
const App = () => {
const getMsg = (msg) => {
console.log(msg)
}
return(
// 绑定属性传
<Son onGetMsg={getMsg}/>
)
}
export default App
- 兄弟组件通信

实现思路: 借助
状态提升
机制,通过共同的父组件进行兄弟之间的数据传递
- A组件先通过子传父的方式把数据传递给父组件App
- App拿到数据之后通过父传子的方式再传递给B组件
// 1. 通过子传父 A -> App
// 2. 通过父传子 App -> B
import { useState } from "react"
function A ({ onGetAName }) {
// Son组件中的数据
const name = 'this is A name'
return (
<div>
this is A compnent,
<button onClick={() => onGetAName(name)}>send</button>
</div>
)
}
function B ({ name }) {
return (
<div>
this is B compnent,
{name}
</div>
)
}
function App () {
const [name, setName] = useState('')
const getAName = (name) => {
setName(name)
}
return (
<div>
this is App
<A onGetAName={getAName} />
<B name={name} />
</div>
)
}
export default App
- 跨层组件通信
实现步骤:
- 使用
createContext
方法创建一个上下文对象Ctx - 在顶层组件(App)中通过
Ctx.Provider
组件提供数据 - 在底层组件(B)中通过
useContext
钩子函数获取消费数据
// App -> A -> B
import { createContext, useContext } from "react"
// 1. createContext方法创建一个上下文对象
export const MsgContext = createContext() //暴露
function A () {
return (
<div>
this is A component
<B />
</div>
)
}
function B () {
// 3. 在底层组件 通过useContext钩子函数使用数据
const msg = useContext(MsgContext)
return (
<div>
this is B compnent,{msg}
</div>
)
}
function App () {
const msg = 'this is app msg'
return (
<div>
{/* 2. 在顶层组件 通过Provider组件提供数据 */}
<MsgContext.Provider value={msg}>
this is App
<A />
</MsgContext.Provider>
</div>
)
}
export default App
13.React副作用管理-useEffect【监听器】
useEffect 挂载完成, 卸载前
useEffect(() => { // 操作 componentDidMount return () => { // 销毁代码 } },[])// 空数组 ,进界面只执行一次
基础使用【代替数据监听 代替生命周期】
需求:在组件渲染完毕之后,立刻从服务端获取平道列表数据并显示到页面中

import { useEffect, useState } from "react";
const URL = 'http://geek.itheima.net/v1_0/channels'
function App() {
const [list ,setList] = useState([])
useEffect( () => {
async function getList() {
const res = await fetch(URL)
const listJson = await res.json()
console.log(listJson);
setList(listJson.data.channels)
}
getList()
} ,[])
return (
<div className="App">
<ul>
{list.map( item => {
return <li key={item.id}>{item.name}</li>
})}
</ul>
</div>
);
}
export default App;
useEffect依赖说明
useEffect副作用函数的执行时机存在多种情况,根据传入依赖项的不同,会有不同的执行表现
依赖项 | 副作用功函数的执行时机 |
---|---|
没有依赖项 | 组件初始渲染 + 组件更新时执行 |
空数组依赖 | 只在初始渲染时执行一次 |
添加特定依赖项 | 组件初始渲染 + 依赖项变化时执行 |
注意
说明:清除副作用的函数最常见的执行时机是在组件卸载时自动执行
import { useEffect, useState } from "react"
function Son () {
// 1. 渲染时开启一个定时器
useEffect(() => {
const timer = setInterval(() => {
console.log('定时器执行中...')
}, 1000)
return () => {
// 清除副作用(组件卸载时)
clearInterval(timer)
}
}, [])
return <div>this is son</div>
}
function App () {
// 通过条件渲染模拟组件卸载
const [show, setShow] = useState(true)
return (
<div>
{show && <Son />}
<button onClick={() => setShow(false)}>卸载Son组件</button>
</div>
)
}
export default App
14.自定义Hook实现
概念:自定义Hook是以
use打头的函数
,通过自定义Hook函数可以用来实现逻辑的封装和复用
// 封装自定义Hook
// 问题: 布尔切换的逻辑 当前组件耦合在一起的 不方便复用
// 解决思路: 自定义hook
import { useState } from "react"
function useToggle () {
// 可复用的逻辑代码
const [value, setValue] = useState(true)
const toggle = () => setValue(!value)
// 哪些状态和回调函数需要在其他组件中使用 return
return {
value,
toggle
}
}
// 封装自定义hook通用思路
// 1. 声明一个以use打头的函数
// 2. 在函数体内封装可复用的逻辑(只要是可复用的逻辑)
// 3. 把组件中用到的状态或者回调return出去(以对象或者数组)
// 4. 在哪个组件中要用到这个逻辑,就执行这个函数,解构出来状态和回调进行使用
function App () {
const { value, toggle } = useToggle()
return (
<div>
{value && <div>this is div</div>}
<button onClick={toggle}>toggle</button>
</div>
)
}
export default App
React Hooks使用规则
- 只能在组件中或者其他自定义Hook函数中调用
- 只能在组件的顶层调用,不能嵌套在if、for、其它的函数中
15.Redux与React - 环境准备
Redux虽然是一个框架无关可以独立运行的插件,但是社区通常还是把它与React绑定在一起使用,以一个计数器案例体验一下Redux + React 的基础使用
1. 配套工具
在React中使用redux,官方要求安装俩个其他插件 - Redux Toolkit 和 react-redux
Redux Toolkit(RTK)- 官方推荐编写Redux逻辑的方式,是一套工具的集合集,简化书写方式
react-redux - 用来 链接 Redux 和 React组件 的中间件

2. 配置基础环境
- 使用 CRA 快速创建 React 项目
npx create-react-app react-redux
- 安装配套工具
npm i @reduxjs/toolkit react-redux
- 启动项目
npm run start
3.使用
1.使用React Toolkit 创建 counterStore
import { createSlice } from "@reduxjs/toolkit";
const counterStore = createSlice({
name:'count',
// 初始化数据
initialState: {
count: 0
},
// 修改数据的同步方法
reducers: {
add(state) {
state.count++
},
noAdd(state) {
state.count--
}
}
})
// 解构action对象的函数
const {add, noAdd} = counterStore.actions
//
const countReducer = counterStore.reducer
// 多处action函数和reducer函数
export{add, noAdd}
export default countReducer
import { configureStore } from "@reduxjs/toolkit";
import countReducer from "./modules/counterStore";
const store = configureStore({
reducer: {
count: countReducer
}
})
export default store
2.为React注入store
import { createRoot } from 'react-dom/client'
import App from './App.jsx'
import { Provider } from 'react-redux'
import store from './store/index.jsx'
createRoot(document.getElementById('root')).render(
<Provider store={store}>
<App />
</Provider>
)
4. React组件使用store中的数据
在React组件中使用store中的数据,需要用到一个钩子函数 - useSelector,它的作用是把store中的数据映射到组件中,使用样例如下:

5. React组件修改store中的数据
React组件中修改store中的数据需要借助另外一个hook函数 - useDispatch,它的作用是生成提交action对象的dispatch函数,使用样例如下:
import { useDispatch, useSelector } from "react-redux"
import { add, noAdd } from "./store/modules/counterStore"
function App() {
const {count} = useSelector(state => state.count)
const dispatch = useDispatch()
return(
<div className="app">
<button onClick={() => dispatch(add())}>+</button>
<span>{count}</span>
<button onClick={() => dispatch(noAdd())}>-</button>
</div>
)
}
export default App
6.Redux与React - 提交action传参
获取组件里面提供参数

toNnm(state,action) {
state.count = action.payload // action.payload传过来的参数
}
7.异步函数
编写一个函数返回一个函数
// 异步函数写入数据
const fetchChannelList = () => {
return async (dispatch) => { // 使用action对象
const res = await axios.get('http://geek.itheima.net/v1_0/channels')
dispatch(setChannelList(res.data.data.channels))
}
}
16.路由快速上手
一个路径 path 对应一个组件 component 当我们在浏览器中访问一个 path 的时候,path 对应的组件会在页面中进行渲染 
2. 创建路由开发环境
使用vite创建项目
npm create vite@latest react-router-pro -- --template react
npm i
安装最新的ReactRouter包
npm i react-router-dom
启动项目
npm run dev
3.路由使用
文件目录
- 编写路由表
import { lazy } from 'react';
import { createBrowserRouter } from 'react-router-dom'
const Login = lazy(() => import('../page/login/index')) // 懒加载
const Article = lazy(() => import('../page/Article/index'))
const router = createBrowserRouter([ // 获取router实例
{
path: '/login',
element: <Login />
},
{
path: 'article',
element: <Article />
}
])
export default router
- 导入router实例
import { createRoot } from 'react-dom/client'
import App from './App.jsx'
import { RouterProvider } from 'react-router-dom'
import router from './router/index.jsx'
createRoot(document.getElementById('root')).render(
<RouterProvider router={router}> // 注入router实例
<App />
</RouterProvider>
)
4.路由跳转
4.1. 声明式导航
声明式导航是指通过在模版中通过
<Link/>
组件描述出要跳转到哪里去,比如后台管理系统的左侧菜单通常使用这种方式进行
import { Link } from "react-router-dom"
const Login = () => {
return (
<div>
我是登录页
<Link *to*='/article'>跳转文章页</Link>
</div>
)
}
export default Login
语法说明:通过给组件的to属性指定要跳转到路由path,组件会被渲染为浏览器支持的a链接,如果需要传参直接通过字符串拼接的方式拼接参数即可
4.2. 编程式导航
编程式导航是指通过 useNavigate
钩子得到导航方法,然后通过调用方法以命令式的形式进行路由跳转,比如想在登录请求完毕之后跳转就可以选择这种方式,更加灵活
import { useNavigate } from "react-router-dom"
const Login = () => {
const navigate = useNavigate()
const toArticle = () => {
navigate('/article')
}
return (
<div>
我是登录页
<button onClick={toArticle}>文章页</button>
</div>
)
}
export default Login
语法说明:通过调用navigate方法传入地址path实现跳转
5.导航传参
5.1.useSearchParams传参
- 跳转书写
navigate('/article?id=100&name=小赵')
- 接收
const [params] = useSearchParams()
console.log(params.get('id'))
console.log(params.get('name'))
5.2.Params传参
- 路由表配置
{
path: '/article/:id?',
element: <Article />
}
- 跳转传参
navigate('/article/101')
- 接收
const params1 = useParams()
console.log(params1.id)
6.二级路由 利用children
- 路由表的编写 children: []
{
path: '/',
element: <Layout />,
// 二级路由表书写
children: [
{
path: 'board',
element: <Board />
},
{
path: 'about',
element: <About />
}
]
},
- 出口编写
const Layout = () => {
return (
<div>
我是Layout
<Link to='/board'>面板 </Link>
<Link to='/about'> 关于</Link>
{/* 二级路由出口 */}
<Outlet /> // 出口
</div>
)
}
7.默认二级路由
将路由表里面的path 去掉 替换为 index: true
- 修改路由表
children: [
{
index: true, // 默认二级路由
element: <Board />
},
{
path: '/about',
element: <About />
}
]
8. 404路由配置 路径找不到
*通配符
{
path: '*', //匹配所有路径, 必须写最后
element: <NotFound />
}
9. 俩种路由模式
各个主流框架的路由常用的路由模式有俩种,history模式和hash模式, ReactRouter分别由 createBrowerRouter 和 createHashRouter 函数负责创建
路由模式 | url表现 | 底层原理 | 是否需要后端支持 |
---|---|---|---|
history | url/login | history对象 + pushState事件 | 需要 |
hash | url/#/login | 监听hashChange事件 | 不需要 |
17.移动库antd-mobile
命令
npm i antd-mobile
antD主题定制
1. 定制方案
2. 实现方式
全局定制
局部定制
18.配置别名路径@
1 安装 @types/node
npm i -D @types/node
2 在 vite.config.ts 中添加配置:@
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';
import path from 'path';
export default defineConfig({
plugins: [
react(),
],
resolve: {
alias: {
'@': path.resolve(__dirname, './src')
},
}
});
3 配置路径别名的提示
虽然现在路径别名已经有了,但是在文件中输入@是没有提示路径的。
需要我们在 tsconfig.json 或 jsconfig.json 中,添加配置项:
如果在使用 vite 创建项目时,选择的是 TypeScript 项目,会自动生成 tsconfig.json 文件; 若选择的是 JavaScript 项目,可能不会自动生成 jsconfig.json 文件,那么我们手动创建一个即可。
jsconfig.json 文件配置:
{
"compilerOptions": {
"baseUrl": ".",
"paths": {
// 解决项目中使用@作为路径别名,导致vscode无法跳转文件的问题
"@/*": ["src/*"]
},
// 解决prettier对于装饰器语法的警告
"experimentalDecorators": true,
// 解决.jsx文件无法快速跳转的问题
"jsx": "preserve"
},
}
19.数据Mock实现
在前后端分类的开发模式下,前端可以在没有实际后端接口的支持下先进行接口数据的模拟,进行正常的业务功能开发
1. 常见的Mock方式
2. json-server实现Mock
实现步骤:
- 项目中安装json-server npm i -D json-server
- 准备一个json文件 (素材里获取)
- 添加启动命令

- 访问接口进行测试
20.组件库antd使用
命令
npm install antd --save
21.使用gitee管理项目
在官网创建一个仓库
然后在控制台输入:git init
上传项目:git add .
git commit -m "干了什么"
git push
git push --set-upstream origin master
22 全局样式初始化
Normalize.css: Make browsers render all elements more consistently.
主文件导入 import 'normalize.css'
23 echarts 图标的使用
安装echarts
npm i echarts
实现基础Demo
import * as echarts from 'echarts'
const Home = () => {
const chartRef = useRef(null)
useEffect(() => {
// 1. 生成实例
const myChart = echarts.init(chartRef.current)
// 2. 准备图表参数
const option = {
xAxis: {
type: 'category',
data: ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun']
},
yAxis: {
type: 'value'
},
series: [
{
data: [120, 200, 150, 80, 70, 110, 130],
type: 'bar'
}
]
}
// 3. 渲染参数
myChart.setOption(option)
}, [])
24 准备富文本编辑器
https://www.wangeditor.co
实现步骤
- 安装富文本编辑器
- 导入富文本编辑器组件以及样式文件
- 渲染富文本编辑器组件
- 调整富文本编辑器的样式
代码落地 1-安装 react-quill
npm i react-quill@2.0.0-beta.2
2-导入资源渲染组件
25 useReducer基础使用
作用: 让 React 管理多个相对关联的状态数据
import { useReducer } from 'react'
// 1. 定义reducer函数,根据不同的action返回不同的新状态
function reducer(state, action) {
switch (action.type) {
case 'INC':
return state + 1
case 'DEC':
return state - 1
default:
return state
}
}
function App() {
// 2. 使用useReducer分派action
const [state, dispatch] = useReducer(reducer, 0)
return (
<>
{/* 3. 调用dispatch函数传入action对象 触发reducer函数,分派action操作,使用新状态更新视图 */}
<button onClick={() => dispatch({ type: 'DEC' })}>-</button>
{state}
<button onClick={() => dispatch({ type: 'INC' })}>+</button>
</>
)
}
export default App
更新流程

分派action传参
做法:分派action时如果想要传递参数,需要在action对象中添加一个payload参数,放置状态参数
// 定义reducer
import { useReducer } from 'react'
// 1. 根据不同的action返回不同的新状态
function reducer(state, action) {
console.log('reducer执行了')
switch (action.type) {
case 'INC':
return state + 1
case 'DEC':
return state - 1
case 'UPDATE':
return state + action.payload
default:
return state
}
}
function App() {
// 2. 使用useReducer分派action
const [state, dispatch] = useReducer(reducer, 0)
return (
<>
{/* 3. 调用dispatch函数传入action对象 触发reducer函数,分派action操作,使用新状态更新视图 */}
<button onClick={() => dispatch({ type: 'DEC' })}>-</button>
{state}
<button onClick={() => dispatch({ type: 'INC' })}>+</button>
<button onClick={() => dispatch({ type: 'UPDATE', payload: 100 })}>
update to 100
</button>
</>
)
}
export default App
25. useMemo 消耗非常大的计算
作用:它在每次重新渲染的时候能够缓存计算的结果
看个场景
下面我们的本来的用意是想基于count的变化计算斐波那契数列之和,但是当我们修改num状态的时候,斐波那契求和函数也会被执行,显然是一种浪费
// useMemo
// 作用:在组件渲染时缓存计算的结果
import { useState } from 'react'
function factorialOf(n) {
console.log('斐波那契函数执行了')
return n <= 0 ? 1 : n * factorialOf(n - 1)
}
function App() {
const [count, setCount] = useState(0)
// 计算斐波那契之和
const sumByCount = factorialOf(count)
const [num, setNum] = useState(0)
return (
<>
{sum}
<button onClick={() => setCount(count + 1)}>+count:{count}</button>
<button onClick={() => setNum(num + 1)}>+num:{num}</button>
</>
)
}
export default App
useMemo缓存计算结果
思路: 只有count发生变化时才重新进行计算
import { useMemo, useState } from 'react'
function fib (n) {
console.log('计算函数执行了')
if (n < 3) return 1
return fib(n - 2) + fib(n - 1)
}
function App() {
const [count, setCount] = useState(0)
// 计算斐波那契之和
// const sum = fib(count)
// 通过useMemo缓存计算结果,只有count发生变化时才重新计算
const sum = useMemo(() => {
return fib(count)
}, [count])
const [num, setNum] = useState(0)
return (
<>
{sum}
<button onClick={() => setCount(count + 1)}>+count:{count}</button>
<button onClick={() => setNum(num + 1)}>+num:{num}</button>
</>
)
}
export default App
React.memouseMemo
作用:它在每次重新渲染的时候能够缓存计算的结果
看个场景
下面我们的本来的用意是想基于count的变化计算斐波那契数列之和,但是当我们修改num状态的时候,斐波那契求和函数也会被执行,显然是一种浪费
// useMemo
// 作用:在组件渲染时缓存计算的结果
import { useState } from 'react'
function factorialOf(n) {
console.log('斐波那契函数执行了')
return n <= 0 ? 1 : n * factorialOf(n - 1)
}
function App() {
const [count, setCount] = useState(0)
// 计算斐波那契之和
const sumByCount = factorialOf(count)
const [num, setNum] = useState(0)
return (
<>
{sum}
<button onClick={() => setCount(count + 1)}>+count:{count}</button>
<button onClick={() => setNum(num + 1)}>+num:{num}</button>
</>
)
}
export default App
useMemo缓存计算结果
思路: 只有count发生变化时才重新进行计算
import { useMemo, useState } from 'react'
function fib (n) {
console.log('计算函数执行了')
if (n < 3) return 1
return fib(n - 2) + fib(n - 1)
}
function App() {
const [count, setCount] = useState(0)
// 计算斐波那契之和
// const sum = fib(count)
// 通过useMemo缓存计算结果,只有count发生变化时才重新计算
const sum = useMemo(() => {
return fib(count)
}, [count])
const [num, setNum] = useState(0)
return (
<>
{sum}
<button onClick={() => setCount(count + 1)}>+count:{count}</button>
<button onClick={() => setNum(num + 1)}>+num:{num}</button>
</>
)
}
export default App
27. React.memo 子组件在Props没变,不会渲染 引用数据类型会渲染,没效果 可以结合 useMemo缓存 完成不渲染
作用:允许组件在props没有改变的情况下跳过重新渲染
组件默认的渲染机制
默认机制:顶层组件发生重新渲染,这个组件树的子级组件都会被重新渲染
// memo
// 作用:允许组件在props没有改变的情况下跳过重新渲染
import { useState } from 'react'
function Son() {
console.log('子组件被重新渲染了')
return <div>this is son</div>
}
function App() {
const [, forceUpdate] = useState()
console.log('父组件重新渲染了')
return (
<>
<Son />
<button onClick={() => forceUpdate(Math.random())}>update</button>
</>
)
}
export default App
使用React.memo优化
机制:只有props发生变化时才重新渲染 下面的子组件通过 memo 进行包裹之后,返回一个新的组件MemoSon, 只有传给MemoSon的props参数发生变化时才会重新渲染
import React, { useState } from 'react'
const MemoSon = React.memo(function Son() {
console.log('子组件被重新渲染了')
return <div>this is span</div>
})
function App() {
const [, forceUpdate] = useState()
console.log('父组件重新渲染了')
return (
<>
<MemoSon />
<button onClick={() => forceUpdate(Math.random())}>update</button>
</>
)
}
export default App
props变化重新渲染
import React, { useState } from 'react'
const MemoSon = React.memo(function Son() {
console.log('子组件被重新渲染了')
return <div>this is span</div>
})
function App() {
console.log('父组件重新渲染了')
const [count, setCount] = useState(0)
return (
<>
<MemoSon count={count} />
<button onClick={() => setCount(count + 1)}>+{count}</button>
</>
)
}
export default App
props的比较机制
对于props的比较,进行的是‘浅比较’,底层使用
Object.is
进行比较,针对于对象数据类型,只会对比俩次的引用是否相等,如果不相等就会重新渲染,React并不关心对象中的具体属性
import React, { useState } from 'react'
const MemoSon = React.memo(function Son() {
console.log('子组件被重新渲染了')
return <div>this is span</div>
})
function App() {
// const [count, setCount] = useState(0)
const [list, setList] = useState([1, 2, 3])
return (
<>
<MemoSon list={list} />
<button onClick={() => setList([1, 2, 3])}>
{JSON.stringify(list)}
</button>
</>
)
}
export default App
说明:虽然俩次的list状态都是 [1,2,3]
, 但是因为组件App俩次渲染生成了不同的对象引用list,所以传给MemoSon组件的props视为不同,子组件就会发生重新渲染
27. useCallback 父传子是函数 引用数据 缓存函数 函数不变 不会导致子组件渲染
看个场景
上一小节我们说到,当给子组件传递一个引用类型
prop的时候,即使我们使用了memo
函数依旧无法阻止子组件的渲染,其实传递prop的时候,往往传递一个回调函数更为常见,比如实现子传父,此时如果想要避免子组件渲染,可以使用 useCallback
缓存回调函数
useCallback缓存函数
useCallback缓存之后的函数可以在组件渲染时保持引用稳定,也就是返回同一个引用
// useCallBack
import { memo, useCallback, useState } from 'react'
const MemoSon = memo(function Son() {
console.log('Son组件渲染了')
return <div>this is son</div>
})
function App() {
const [, forceUpate] = useState()
console.log('父组件重新渲染了')
const onGetSonMessage = useCallback((message) => {
console.log(message)
}, [])
return (
<div>
<MemoSon onGetSonMessage={onGetSonMessage} />
<button onClick={() => forceUpate(Math.random())}>update</button>
</div>
)
}
export default App
28. forwardRef 透传 父组件获取子组件的DOM
作用:允许组件使用ref将一个DOM节点暴露给父组件
import { forwardRef, useRef } from 'react'
const MyInput = forwardRef(function Input(props, ref) {
return <input {...props} type="text" ref={ref} />
}, [])
function App() {
const ref = useRef(null)
const focusHandle = () => {
console.log(ref.current.focus())
}
return (
<div>
<MyInput ref={ref} />
<button onClick={focusHandle}>focus</button>
</div>
)
}
export default App
29 useImperativeHandle 父组件 获取子组件的方法
作用:如果我们并不想暴露子组件中的DOM而是想暴露子组件内部的方法
import { forwardRef, useImperativeHandle, useRef } from 'react'
const MyInput = forwardRef(function Input(props, ref) {
// 实现内部的聚焦逻辑
const inputRef = useRef(null)
const focus = () => inputRef.current.focus()
// 暴露子组件内部的聚焦方法
useImperativeHandle(ref, () => {
return {
focus,
}
})
return <input {...props} ref={inputRef} type="text" />
})
function App() {
const ref = useRef(null)
const focusHandle = () => ref.current.focus()
return (
<div>
<MyInput ref={ref} />
<button onClick={focusHandle}>focus</button>
</div>
)
}
export default App
30. ZUSTAND 公共状态数据的使用
效果和数据Redux一样
import { useEffect } from 'react'
import { create } from 'zustand'
const URL = 'http://geek.itheima.net/v1_0/channels'
const store = create((set) => {
// 需要返回一个对象
return {
num: 1, //初始变量
add: () => set((state) => ({ num: state.num + 1 })), // set是一个固定方法 修改变量的值的方法 需要返回一个对象
// 如果需要用到原有的值 须在里面再写一个函数 用state获取
// 异步方法
channelList: [],
fetchChannelList: async () => {
const res = await fetch(URL)
const jsonData = await res.json()
set({ channelList: jsonData.data.channels })
}
}
})
function App() {
// const num = store(state => state.num)
const { num, add, fetchChannelList, channelList } = store()
// 获取数据
useEffect(() => {
fetchChannelList()
}, [fetchChannelList])
useEffect(() => {
const str = channelList.map(item => (
<div key={item.id}>{item.name}</div>
))
console.log(str)
})
// console.log(channelList)
return (
<>
{num}
<button onClick={add}>加一</button>
{channelList.map(item => (
<div key={item.id}>{item.name}</div>
))}
</>
)
}
export default App
切片模式
场景:当我们单个store比较大的时候,可以采用一种切片模式
进行模块拆分再组合
拆分并组合切片
import { create } from 'zustand'
// 创建counter相关切片
const createCounterStore = (set) => {
return {
count: 0,
setCount: () => {
set(state => ({ count: state.count + 1 }))
}
}
}
// 创建channel相关切片
const createChannelStore = (set) => {
return {
channelList: [],
fetchGetList: async () => {
const res = await fetch(URL)
const jsonData = await res.json()
set({ channelList: jsonData.data.channels })
}
}
}
// 组合切片
const useStore = create((...a) => ({
...createCounterStore(...a),
...createChannelStore(...a)
}))
组件使用
function App() {
const {count, inc, channelList, fetchChannelList } = useStore()
return (
<>
<button onClick={inc}>{count}</button>
<ul>
{channelList.map((item) => (
<li key={item.id}>{item.name}</li>
))}
</ul>
</>
)
}
export default App
对接DevTools
简单的调试我们可以安装一个 名称为 simple-zustand-devtools 的调试工具
安装调试包
npm i simple-zustand-devtools -D
配置调试工具
import create from 'zustand'
// 导入核心方法
import { mountStoreDevtool } from 'simple-zustand-devtools'
// 省略部分代码...
// 开发环境开启调试
if (process.env.NODE_ENV === 'development') {
mountStoreDevtool('channelStore', useChannelStore)
}
export default useChannelStore