学习视频:b站:黑马程序员前端React18入门到实战视频教程
简介
优势
相较于传统基于DOM开发的优势
- 组件化的开发方式:提高开发效率和组件复用性
- 不错的性能
- 采用虚拟dom
相较于其它前端框架的优势
开始
创建
使用cra创建项目
create-react-app是一个快速创建React开发环境的工具,底层由Webpack构件,封装了配置细节,开箱即用npm i -g create-react-appnpx create-react-app 项目名
npx - Node.js工具命令,查找并执行后续的包命令
项目目录:
- src
- App.js根组件
- index.js入口文件
app引入到index,再渲染到public/index.html
使用vite创建项目
npm create vite@latest 项目名 -- --template react
JSX
JSX是JavaScript和XMl(HTML)的缩写,表示在JS代码中编写HTML模版结构,它是React中构建UI的方式
优势:
- HTML的声明式模版写法
- JavaScript的可编程能力
本质:
JSX并不是标准的JS语法,它是 JS的语法扩展,浏览器本身不能识别,需要通过解析工具做解析之后才能在浏览器中使用
通过babel编译
js表达式
使用{}识别js中的表达式:
- 使用引号传递字符串
{'str'} - 使用JS变量
{count} - 函数调用和方法调用
{new Date().time()} - 使用JavaScript对象
<div style={{color: 'red'}}></div>
列表渲染
const list = [
{id: 1, name: 'John'},
{id: 2, name: 'John1'},
{id: 3, name: 'John2'},
]
function App() {
return (
<div className="App">
<ul>
{list.map(item => <li key={item.id}>{item.name}</li>)}
</ul>
</div>
);
}
条件渲染
const isLogin = false;
function App() {
return (
<div className="App">
<p>{isLogin && 'true显示'}</p>
<p>{isLogin ? <span>true显示</span> : <span>false显示</span>}</p>
</div>
);
}
function typeChange(type) {
if (type === 1) {
return <div>111</div>;
} else if (type === 2) {
return <div>222</div>;
} else {
return <div>333</div>;
}
}
function App() {
return (
<div className="App">
{typeChange(2)}
</div>
);
}
事件绑定
React中的事件绑定,通过语法 on + 事件名称 = { 事件处理程序 },整体上遵循驼峰命名法
function btnClick() {
console.log('btnClick')
}
function btnClick2(e) {
// 事件对象
console.log(e)
}
function btnClick3(name) {
console.log(name)
}
function btnClick4(e, name) {
console.log(e, name)
}
function App() {
return (
<div className="App">
<button onClick={btnClick}>onClick</button>
<button onClick={btnClick2}>onClick2</button>
<button onClick={() => btnClick3('name')}>onClick3</button>
<button onClick={(e) => btnClick4(e, 'name')}>onClick4</button>
</div>
);
}
组件
一个组件就是一个用户界面的一部分,它可以有自己的逻辑和外观,组件之间可以互相嵌套,也可以服用多次
基础使用
在React中,一个组件就是首字母大写的函数,内部存放了组件的逻辑和视图UI, 渲染组件只需要把组件当成标签书写即可
function MyButton() {
return <button>MyButton</button>
}
function App() {
return (
<div className="App">
<MyButton />
<MyButton></MyButton>
</div>
);
}
useState声明状态数据
useState 是一个 React Hook(函数),它允许我们向组件添加一个状态变量, 从而控制影响组件的渲染结果
和普通JS变量不同的是,状态变量一旦发生变化组件的视图UI也会跟着变化(数据驱动视图)
import { useState } from 'react';
function App() {
// 调用useState方法创建一个状态变量,并返回该变量的当前值和一个更新该变量的函数。
const [count, setCount] = useState(0);
const handleClick = () => {
// 在React中状态被认为是只读的,我们应该始终`替换它而不是修改它`,
// 直接修改状态不能引发视图更新
setCount(count + 1);
}
return (
<div className="App">
<button onClick={handleClick}>{count}</button>
</div>
);
}
import { useState } from 'react';
function App() {
// 调用useState方法创建一个状态变量,并返回该变量的当前值和一个更新该变量的函数。
const [count, setCount] = useState({
num: 1
});
const handleClick = () => {
// 对于对象类型的状态变量,应该始终给set方法一个`全新的对象` 来进行修改
setCount({
...count,
num: count.num + 1
});
}
return (
<div className="App">
<button onClick={handleClick}>{count.num}</button>
</div>
);
}
组件基础样式
.btn {
background-color: aqua;
}
import './index.css'
const btn = {
backgroundColor: 'red'
}
function App() {
return (
<div className="App">
{/* class类名控制 */}
<button className='btn'>111</button>
{/* 行内样式-不推荐 */}
<button style={btn}>222</button>
</div>
);
}
表单受控绑定-类似双向数据绑定
import {useState} from "react";
function App() {
const [inputValue, setInputValue] = useState('')
return (
<div className="App">
<input value={inputValue} onChange={(e) => setInputValue(e.target.value)}/>
</div>
);
}
获取dom
import {useRef} from "react";
function App() {
const inputDom = useRef(null);
const btnClick = () => {
// 渲染完才可以获取dom
console.log(inputDom.current);
}
return (
<div className="App">
<input type="text" ref={inputDom}/>
<button onClick={() => btnClick()}>log dom</button>
</div>
);
}
组件通信
父传子
function Son(props) {
console.log(props.children)
return <div>{props.sonName}</div>
}
function App() {
const sonName = '我是子组件'
return (
<div className="App">
<Son sonName={sonName}>
在自子组件中嵌套的内容会在props的children属性接收
</Son>
</div>
);
}
props可以传递任意合法数据:数字、字符串、布尔值、数组、对象、函数、JSX
props是只读的:父组件传递的数据只能由父组件修改
子传父
import {useState, useEffect} from 'react';
function Son({onGetSonMsg}) {
// useEffect 钩子旨在处理副作用,应用于渲染后需要发生的状态更新
useEffect(() => {
// 如果直接使用会报错,因为会在渲染时更新父组件的数据
// 这样违反了react组件生命周期规则
// React 的单向数据流不允许子组件在渲染过程中对父组件产生副作用
onGetSonMsg('子组件传来的数据');
})
return <div>子组件</div>
}
function App() {
const [sonMsg, setSonMsg] = useState('')
const getSonMsg = (msg) => {
setSonMsg(msg)
}
return (
<div className="App">
{sonMsg}
<Son onGetSonMsg={getSonMsg}></Son>
</div>
);
}
useEffect
useEffect是一个React Hook函数,用于在React组件中创建不是由事件引起而是由渲染本身引起的操作(副作用), 比 如发送AJAX请求,更改DOM等等 useEffect(() => {}, [])
- 参数1是副作用函数
- 参数二可选,放置依赖项 | 依赖项 | 副作用功函数的执行时机 | | --- | --- | | 没有依赖项 | 组件初始渲染 + 组件更新时执行 | | 空数组依赖 | 只在初始渲染时执行一次 | | 添加特定依赖项 | 组件初始渲染 + 依赖项变化时执行 |
import {useEffect, useState} from 'react';
function App() {
const [count, setCount] = useState(0)
useEffect(() => {
console.log('count变化了');
}, [count]);
return (
<div className="App">
{count}
<button onClick={() => setCount(count + 1)}>count++</button>
</div>
);
}
副作用
副作用操作:在useEffect中编写的由渲染本身引起的对接组件外部的操作
import {useEffect, useState} from 'react';
function B() {
let intervalId = setInterval(() => {
console.log('定时器执行中');
}, 2000)
useEffect(() => {
return () => {
// 清除副作用(组件卸载时)
clearInterval(intervalId)
}
})
return <div>B</div>
}
function App() {
const [showB, setShowB] = useState(true)
return (
<div className="App">
{showB && <B></B>}
<button onClick={() => setShowB(false)}>销毁B</button>
</div>
);
}
兄弟组件通信
状态提升:兄弟1 =》父组件 =》兄弟2
import {useState, useEffect} from 'react';
function Brother1({onGetBrother1Msg}) {
useEffect(() => {
onGetBrother1Msg('兄弟组件1传递给兄弟组件2的信息')
})
}
function Brother2({brother1Msg}) {
return <div>{brother1Msg}</div>
}
function App() {
const [brother1Msg, setBrother1Msg] = useState('');
return (
<div className="App">
<Brother1 onGetBrother1Msg={setBrother1Msg}></Brother1>
<Brother2 brother1Msg={brother1Msg}></Brother2>
</div>
);
}
跨层通信
父子通信也适用
import {createContext, useContext} from 'react';
function A () {
return <div>
<B></B>
</div>
}
function B () {
// 底层:使用useContext接收信息
const msg = useContext(crtMsg)
return <div>{msg}</div>
}
// 使用createContext创建上下文对象
const crtMsg = createContext()
function App() {
const msg = '这是顶层的信息'
return (
<div className="App">
{/* 顶层提供数据 */}
<crtMsg.Provider value={msg}>
<A></A>
</crtMsg.Provider>
</div>
);
}
hook
自定义hook
import {useState} from 'react';
// 声明一个以use打头的函数
function useToggle() {
const [showB, setShowB] = useState(true)
const toggle = () => setShowB(!showB)
// 把组件中用到的状态或者回调return出去(以对象或者数组)
return {
showB,
toggle
}
}
function App() {
// 在哪个组件中要用到这个逻辑,就执行这个函数,解构出来状态和回调进行使用
const {showB, toggle} = useToggle()
return (
<div className="App">
{showB && 'bbbbbbbbbbb'}
<button onClick={toggle}>销毁B</button>
</div>
);
}
react hooks使用规则
redux
Redux 是React最常用的集中状态管理工具,类似于Vue中的Pinia(Vuex),可以独立于框架运行
**作用:**通过集中管理的方式管理应用的状态
优势:
- 独立于组件,无视组件之间的层级关系,简化通信问题
- 单项数据流清晰,易于定位bug
- 调试工具配套良好,方便调试
三个核心概念:
配套工具
npm i @reduxjs/toolkit react-redux
工作目录举例
修改、使用store中的数据
import {createSlice} from "@reduxjs/toolkit"
const counterStore = createSlice({
name: 'counter',
// 初始化state
initialState: {
count: 0
},
// 修改状态的方法,支持同步修改
reducers: {
inscrement(state) {
state.count += 1
},
decrement(state) {
state.count -= 1
},
// action传参
incrementByAmount(state, action) {
state.count += action.payload
}
}
})
// 解构出actionCreater函数
const {inscrement, decrement} = counterStore.actions
// 获取reducer
const reducer = counterStore.reducer
// 导出actionCreater
export {inscrement, decrement}
// 导出reducer
export default reducer
import { configureStore } from "@reduxjs/toolkit";
// 导入子模块
import counterReducer from './modules/counterStore';
const store = configureStore({
reducer: {
counterReducer
}
})
export default store
// 导入store
import store from './store';
// 导入store提供组件Provider
import { Provider } from 'react-redux'
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
// 提供store数据
<Provider store={store}>
<App />
</Provider>
);
import { useDispatch, useSelector } from "react-redux";
// 导入创建action对象的方法
import { inscrement, decrement, incrementByAmount } from "./store/modules/counterStore";
function App() {
// 使用state中的数据
const {count} = useSelector(state => state.counterReducer)
// 创建dispatch函数,用于修改数据
const dispatch = useDispatch()
return (
<div className="App">
<button onClick={() => dispatch(decrement())}>-</button>
{count}
<button onClick={() => dispatch(inscrement())}>+</button>
{/* action传参 */}
<button onClick={() => dispatch(incrementByAmount(10))}>+10</button>
</div>
);
}
export default App;
给state设置异步请求数据
import { createSlice } from "@reduxjs/toolkit";
import axios from 'axios';
const channelStore = createSlice({
name: "channel",
initialState: {
channelList: []
},
reducers: {
setChannelList(state, action) {
state.channelList = action.payload;
}
}
})
// 封装一个函数 在函数中return一个新函数 在新函数中封装异步
// 得到数据之后通过dispatch函数 触发修改
const fetchChannelList = () => {
return async (dispatch) => {
const res = await axios.get('http://geek.itheima.net/v1_0/channels');
dispatch(setChannelList(res.data.data.channels))
}
}
// 解构actions
const { setChannelList } = channelStore.actions
// 获取reducer
const reducer = channelStore.reducer
export {fetchChannelList}
export default reducer
store/index.js引入
import { useDispatch, useSelector } from "react-redux";
// 导入创建action对象的方法
import { fetchChannelList } from "./store/modules/channelStore";
import {useEffect} from 'react'
function App() {
// 使用state中的数据
const {channelList} = useSelector(state => state.channelReducer)
// 创建dispatch函数,用于修改数据
const dispatch = useDispatch()
useEffect(() => {
dispatch(fetchChannelList())
}, [])
return (
<div className="App">
{JSON.stringify(channelList)}
</div>
);
}
export default App;
浏览器调试工具
reactRouter
安装
使用
目录
const Artical = () => {
return (
<div>artical</div>
)
}
export default Artical
import Artical from "../pages/Artical";
import Login from "../pages/Login";
import { createBrowserRouter } from "react-router-dom";
const router = createBrowserRouter([
{
path: '/login',
element: <Login />
},
{
path: '/article',
element: <Artical />
}
])
export default router
import {RouterProvider} from 'react-router-dom'
import router from './router/index.jsx'
ReactDOM.createRoot(document.getElementById('root')).render(
<React.StrictMode>
{/* <App /> */}
<RouterProvider router={router} />
</React.StrictMode>,
)
路由导航
import { Link, useNavigate } from "react-router-dom"
const Login = () => {
const navigate = useNavigate()
return (
<div>
Login
{/* 声明式导航 */}
<Link to='/article'>跳转到/article</Link>
{/* 编程式导航 */}
<button onClick={() => navigate('/article')}>跳转到/article</button>
</div>
)
}
export default Login
路由传参
import { useNavigate } from "react-router-dom"
const Login = () => {
const navigate = useNavigate()
return (
<div>
Login
{/* searchParams传参 */}
<button onClick={() => navigate('/article?id=100')}>searchParams传参</button>
{/* params传参 */}
<button onClick={() => navigate('/article/200')}>params传参</button>
</div>
)
}
export default Login
import { useParams, useSearchParams } from "react-router-dom"
const Artical = () => {
{/* searchParams传参 */}
const [params] = useSearchParams();
let id1 = params.get('id');
// params传参
const param2 = useParams();
let id2 = param2.id;
return (
<div>
artical
<br />
{id1}
<br />
{id2}
</div>
)
}
export default Artical
// searchParams传参
{
path: '/article',
element: <Artical />
},
// params传参
{
path: '/article/:id',
element: <Artical />
}
嵌套路由
{
path: '/login',
element: <Login />,
// 嵌套路由
children: [
{
// 默认路由
index: true,
element: <Child1 />,
},
{
path: 'child2',
element: <Child2 />,
}
]
}
import { Outlet } from "react-router-dom"
const Login = () => {
return (
<div>
Login
{/* 二级路由渲染位置 */}
<Outlet />
</div>
)
}
export default Login
404路由配置
- 准备一个NotFound组件
const NotFound = () => {
return (
<div>404</div>
)
}
export default NotFound;
- 在路由表数组的末尾,以*号作为路由path配置路由
{
path: '*',
element: <NotFound/>
}
2种路由模式
各个主流框架的路由常用的路由模式有俩种,history模式和hash模式, ReactRouter分别由 createBrowerRouter 和 createHashRouter 函数负责创建
| 路由模式 | url表现 | 底层原理 | 是否需要后端支持 |
|---|---|---|---|
| history | url/login | history对象 + pushState事件 | 需要 |
| hash | url/#/login | 监听hashChange事件 | 不需要 |
hash
// 使用 addEventListener 监听 hashchange 事件:
window.addEventListener('hashchange', function() {
console.log('hash值被修改了')
}, false);
// 使用 onhashchange 事件处理程序
function locationHashChanged() {
if (location.hash === '#/about') {
console.log("欢迎进入about页面");
}
}
window.onhashchange = locationHashChanged;
history
history 是 HTML5 提供的新特性,允许开发者直接更改前端路由,也就是更改 url 地址而无需向后端发送 http 请求。
history.pushState() 向当前浏览器历史中添加记录,方法接收三个参数:
- state:一个对象,popState 事件触发时,state 对象会传入回调函数。如无需传参,则设置为 null
- title:新页面的标题,但是所有浏览器目前都忽略这个值,因此可以设置为空字符串 "" 或者 null
- url:新的网址地址,必须与当前页面处于同一个域下,浏览器的地址栏将显示这个网址
window.onpopstate 事件用来监听浏览历史记录变化,在同一页面的两个历史条目直接跳转触发
库
loadsh排序库:npm i lodash
classnames库:npm install classnames
import classnames from 'classnames';
<span
className={classnames('nav-item', {'active': type === item.type})}
>
text
</span>
uuid: 生成唯一id
dayjs:时间格式化
json-server:
模拟json数据
前端接口神器之 json-server 详细使用指南