基础
安装
<!-- 创建项目 -->
npx create-react-app demo
<!-- 进入项目 -->
cd demo
<!-- 启动项目 -->
npm start
Class组件
State
// 清空app.js
// 写一个点击按钮增加count的示例
import React, { Component } from "react"
class App extends Component {
constructor(props) {
super(props)
this.state = {
count: 0,
}
}
render() {
return (
<div>
<p>当前计数:{this.state.count}</p>
<button onClick={() => this.setState({ count: this.state.count + 1 })}>点击增加</button>
</div>
)
}
}
export default App
生命周期
// react的生命周期
// 1. 挂载时的生命周期
// 2. 更新时的生命周期
// 3. 卸载时的生命周期
// 4. 错误处理时的生命周期
// 1. 挂载时的生命周期
// 1.1 constructor
// 1.2 render
// 1.3 componentDidMount
componentDidMount() {
console.log("1. componentDidMount")
}
// 2. 更新时的生命周期
// 2.1 render
// 2.2 componentDidUpdate
componentDidUpdate() {
console.log("2. componentDidUpdate")
}
// 3. 卸载时的生命周期
// 3.1 componentWillUnmount
componentWillUnmount() {
console.log("3. componentWillUnmount")
}
// 4. 错误处理时的生命周期
// 4.1 componentDidCatch
componentDidCatch(error, info) {
console.log("4. componentDidCatch")
}
// 挂载阶段
// constructor初始化state和方法绑定
// 派生prop,根据prop调整state
// render
// 挂载后执行didMount
class App extends Component {
constructor(props) { // 1. 最先执行,初始化 state
super(props)
this.state = { count: 0 }
}
static getDerivedStateFromProps() { // 2. 在 render 前调用(您代码中未使用)
// 可在此根据 props 调整 state
return null
}
componentDidMount() { // 4. 组件挂载后执行(您代码中未使用)
// 适合发起网络请求、添加订阅等副作用操作
}
render() { // 3. 必须实现,返回 JSX
return (
<div>
<p>当前计数:{this.state.count}</p>
<button onClick={/* ... */}>点击增加</button>
</div>
)
}
}
// 更新阶段
// 决定是否重新渲染
// snapShot获取更新前的dom信息
// render
// didUpdate()
class App extends Component {
shouldComponentUpdate(nextProps, nextState) { // 1. 决定是否重新渲染(未使用)
// 可在此进行性能优化,比如比较新旧 state
return true
}
getSnapshotBeforeUpdate(prevProps, prevState) { // 3. 在 DOM 更新前调用(未使用)
// 可捕获更新前的滚动位置等 DOM 信息
return null
}
componentDidUpdate(prevProps, prevState, snapshot) { // 4. 更新完成后执行(未使用)
// 适合执行 DOM 操作或发起网络请求(需比较 props 变化)
}
render() { // 2. 重新执行渲染
// 当点击按钮调用 setState 时会触发此阶段
}
}
// 卸载
class App extends Component {
componentWillUnmount() { // 组件卸载前执行(未使用)
// 适合清除定时器、取消网络请求等清理操作
}
}
// 错误处理
class App extends Component {
static getDerivedStateFromError(error) { // 后代组件抛出错误时调用
return { hasError: true }
}
componentDidCatch(error, info) { // 捕获组件树错误
// 可在此记录错误日志
}
}
注:
- render为纯函数
- constructor()
初始化state
// 实例
// app.js
import React, { Component } from "react"
class App extends Component {
constructor(props) {
super(props)
this.state = {
data: [],
isLoading: true,
}
}
componentDidMount() {
this.init()
}
async init() {
try {
const res = await fetch("https://jsonplaceholder.typicode.com/posts")
// const res = await fetch("https://clwy.cn/video/api/v2/courses/html.json")
const data = await res.json()
this.setState({ data })
} catch (error) {
console.log(error)
} finally {
this.setState({ isLoading: false })
}
}
render() {
const { data, isLoading } = this.state
return (
<div style={{ width: "100%", height: "100vh", flex: 1, padding: 24 }}>
{isLoading ? (
"加载中..."
) : (
<div>
<h2>{data[0].id}</h2>
<p>{data[0].title}</p>
</div>
)}
</div>
)
}
}
export default App
事件处理
- 事件绑定
import React, { Component } from "react"
class App extends Component {
constructor(props) {
super(props)
this.state = {
school: "wwxx",
}
// 事件绑定
// 为了在回调中使用this,这个绑定必不可少
this.handleClick = this.handleClick.bind(this)
}
handleClick() {
console.log(this.state.school)
}
componentDidMount() {}
render() {
return (
<div>
<button onClick={this.handleClick}>点击</button>
</div>
)
}
}
export default App
- class fields写法,解决this指向问题
import React, { Component } from "react"
class App extends Component {
constructor(props) {
super(props)
this.state = {
school: "wwxx",
}
}
// 事件绑定
// class fields语法
handleClick = () => {
console.log(this.state.school)
}
render() {
return (
<div>
<button onClick={this.handleClick}>点击</button>
</div>
)
}
}
export default App
- 在回调中使用箭头函数
import React, { Component } from "react"
class App extends Component {
constructor(props) {
super(props)
this.state = {
school: "wwxx",
}
}
// 事件绑定
// class fields语法
handleClick = () => {
console.log(this.state.school)
}
render() {
return (
<div>
<button onClick={() => this.handleClick()}>点击</button>
</div>
)
}
}
export default App
组件&Props
- 组件,props
import React, { Component } from "react"
class Welcome extends Component {
render() {
return <h1>Hello, {this.props.name}</h1>
}
}
class App extends Component {
render() {
return (
<div>
<Welcome name="React" />
<Welcome name="React2" />
<Welcome name="React3" />
</div>
)
}
}
export default App
// 注意:props是只读的,不能直接修改
- 子组件调用父组件的方法,并传递参数
import React, { Component } from "react"
class Welcome extends Component {
render() {
return <h1 onClick={() => this.props.click(this.props.name)}>Hello, {this.props.name}</h1>
}
}
class App extends Component {
handleClick(val) {
console.log("val:", val)
}
render() {
return (
<div>
<Welcome name="React" click={(val) => this.handleClick(val)} />
<Welcome name="React2" click={(val) => this.handleClick(val)} />
<Welcome name="React3" click={(val) => this.handleClick(val)} />
</div>
)
}
}
export default App
条件渲染
import React, { Component } from "react"
class App extends Component {
constructor(props) {
super(props)
this.state = {
show: true,
}
}
handleClick() {
this.setState((state) => ({
show: !state.show,
}))
}
render() {
const { show, val } = this.state
return (
<div>
{show && <p>显示出来了</p>}
<button onClick={() => this.handleClick()}>点击这里</button>
</div>
)
}
}
export default App
列表循环
import React, { Component } from "react"
class App extends Component {
constructor(props) {
super(props)
this.state = {
data: [],
isLoading: false,
}
}
componentDidMount() {
this.init()
}
async init() {
const res = await fetch("https://jsonplaceholder.typicode.com/todos")
const data = await res.json()
this.setState({
data,
isLoading: true,
})
}
render() {
const { data, isLoading } = this.state
const courseItem = data.map((item) => <li key={item.id}>{item.title}</li>)
return <div>{isLoading && <ul>{courseItem}</ul>}</div>
}
}
export default App
表单
import React, { Component } from "react"
class App extends Component {
constructor(props) {
super(props)
this.state = {
name: "",
}
}
handleChange = (e) => {
this.setState({
name: e.target.value,
})
}
handleSubmit = (e) => {
e.preventDefault()
console.log("提交", this.state.name)
}
render() {
return (
<div>
你输入的名字:{this.state.name}
<form onSubmit={this.handleSubmit}>
<input type="text" value={this.state.name} onChange={this.handleChange} />
<input type="submit" value="提交" />
</form>
</div>
)
}
}
export default App
// 注意:react没有双向绑定,只有单向绑定
hook(函数式)
Fragments
import React, { Component } from "react"
class Table extends Component {
render() {
return (
<table>
<tr>
<Columns />
</tr>
</table>
)
}
}
// 存在的问题:
// return只能返回一个元素,但是tr中有多个th,所以需要使用数组
// 解决:使用React.Fragment或者缩写
class Columns extends Component {
render() {
return (
// React.Fragment
// <React.Fragment>
// <td>姓名</td>
// <td>年龄</td>
// <td>性别</td>
// </React.Fragment>
// 缩写
<>
<td>姓名</td>
<td>年龄</td>
<td>性别</td>
</>
)
}
}
export default Table
useState
import React, { useState } from "react"
function App() {
const [count, setCount] = useState(0)
return (
<div>
<p>当前计数:{count}</p>
<button onClick={() => setCount(count + 1)}>增加</button>
</div>
)
}
export default App
// 注意:hook相关方法只能在function中使用,不能使用在Class中
- 示例
import React, { useState } from "react"
function App() {
const [data, setData] = useState({
courses: [
{ id: 1, name: "React" },
{ id: 2, name: "Vue" },
{ id: 3, name: "Angular" },
],
})
return (
<ul>
{data.courses.map((item) => (
<li key={item.id}>{item.name}</li>
))}
</ul>
)
}
export default App
useEffect入门
import React, { useState, useEffect } from "react"
import axios from "axios"
function App() {
const [data, setData] = useState({
courses: [],
})
const fetchData = async () => {
const res = await axios.get("https://jsonplaceholder.typicode.com/todos")
console.log(data)
setData({
courses: res.data,
})
}
// 注意:useEffect 中添加了依赖项 [],表示只在组件挂载时执行一次
// 如果不加fetchData 会无限循环,无限打印data
// ∵useEffect相当于componentDidMount和componentDidUpdate
// 第一次componentDidMount,会打印data
// 之后componentDidUpdate,会打印data
useEffect(() => {
fetchData()
}, [])
return (
<ul>
{data.courses.map((item) => (
<li key={item.id}>{item.title}</li>
))}
</ul>
)
}
export default App
useEffect搜索课程
import React, { useState, useEffect } from "react"
import axios from "axios"
function App() {
const [data, setData] = useState({
courses: [],
})
const [keyword, setKeyword] = useState("")
const fetchData = async () => {
const res = await axios.get("https://jsonplaceholder.typicode.com/todos")
console.log(data)
setData({
courses: res.data,
})
}
// 当keyword变化时,调用fetchData
useEffect(() => {
fetchData()
}, [keyword])
return (
<>
<input type="text" value={keyword} onChange={(e) => setKeyword(e.target.value)} />
--------
{keyword}
--------
<ul>
{data.courses
.filter((item) => item.title.includes(keyword))
.map((item) => (
<li key={item.id}>{item.title}</li>
))}
</ul>
</>
)
}
export default App
useEffect实现加载中
import React, { useState, useEffect } from "react"
import axios from "axios"
function App() {
const [data, setData] = useState({
courses: [],
})
const [keyword, setKeyword] = useState("")
const [loading, setLoading] = useState(false)
const fetchData = async () => {
setLoading(true)
const res = await axios.get("https://jsonplaceholder.typicode.com/todos")
console.log(data)
setData({
courses: res.data,
})
setLoading(false)
}
useEffect(() => {
fetchData()
}, [keyword])
return (
<>
<input type="text" value={keyword} onChange={(e) => setKeyword(e.target.value)} />
--------
{keyword}
--------
{loading ? (
<div> "加载中。。。"</div>
) : (
<ul>
{data.courses
.filter((item) => item.title.includes(keyword))
.map((item) => (
<li key={item.id}>{item.title}</li>
))}
</ul>
)}
</>
)
}
export default App
useEffect中的错误处理
import React, { useState, useEffect } from "react"
import axios from "axios"
function App() {
const [data, setData] = useState({
courses: [],
})
const [keyword, setKeyword] = useState("")
const [loading, setLoading] = useState(false)
const [error, setError] = useState(false)
const fetchData = async () => {
setLoading(true)
setError(false)
try {
const res = await axios.get("https://jsonplaceholder.typicode.com/todos")
console.log(data)
setData({
courses: res.data,
})
setLoading(false)
} catch (error) {
setError(true)
setLoading(false)
}
}
useEffect(() => {
fetchData()
}, [keyword])
if (error) {
return (
<>
<div>数据加载失败</div>
</>
)
}
return (
<>
<input type="text" value={keyword} onChange={(e) => setKeyword(e.target.value)} />
--------
{keyword}
--------
{loading ? (
<div> "加载中。。。"</div>
) : (
<ul>
{data.courses
.filter((item) => item.title.includes(keyword))
.map((item) => (
<li key={item.id}>{item.title}</li>
))}
</ul>
)}
</>
)
}
export default App
自定义hook
// 重复代码抽离出来,创建useFetchData.js
import React, { useState, useEffect } from "react"
import axios from "axios"
const useFetchData = (url, initData) => {
const [data, setData] = useState(initData || [])
const [loading, setLoading] = useState(false)
const [error, setError] = useState(false)
const fetchData = async () => {
setLoading(true)
setError(false)
try {
const res = await axios.get(url)
console.log(data)
setData({
courses: res.data,
})
setLoading(false)
} catch (error) {
setError(true)
setLoading(false)
}
}
useEffect(() => {
fetchData(url)
}, [url])
return { data, setData, loading, setLoading, error, setError, fetchData }
}
export default useFetchData
// app.js
import React, { useState, useEffect } from "react"
import axios from "axios"
import useFetchData from "./useFetchData"
function App() {
const api = "https://jsonplaceholder.typicode.com/todos"
const [url, setURL] = useState(api)
const { data, loading, error, fetchData } = useFetchData(url, { courses: [] })
if (error) {
return (
<>
<div>数据加载失败</div>
</>
)
}
return (
<>
<input type="text" onChange={(e) => setURL(`${api}?q=${e.target.value}`)} />
{loading ? (
<div> "加载中。。。"</div>
) : (
<ul>
{data.courses.map((item) => (
<li key={item.id}>{item.title}</li>
))}
</ul>
)}
</>
)
}
export default App
useReducer统一管理状态
import { useEffect, useReducer } from "react"
import axios from "axios"
// 初始状态
let initialState = {
data: [],
loading: false,
error: false,
}
// reducer统一管理
// 1. 管理data
// 2. 管理loading
// 3. 管理error
const reducer = (state, action) => {
switch (action.type) {
case "setData":
return {
...state,
data: action.data,
}
case "setLoading":
return {
...state,
loading: action.loading,
}
case "setError":
return {
...state,
error: action.error,
}
default:
throw new Error("未知的 action 类型")
}
}
const useFetchData = (url, initData) => {
initialState = { ...initialState, data: initData || [] }
const [state, dispatch] = useReducer(reducer, initialState)
const fetchData = async () => {
dispatch({ type: "setLoading", loading: true })
dispatch({ type: "setError", error: false })
try {
const res = await axios.get(url)
dispatch({ type: "setData", data: { courses: res.data } })
dispatch({ type: "setLoading", loading: false })
} catch (error) {
dispatch({ type: "setError", error: true })
dispatch({ type: "setLoading", loading: false })
}
}
useEffect(() => {
fetchData(url)
}, [url])
return { ...state, fetchData }
}
export default useFetchData
useContext
import React, { useState, useEffect } from "react"
import axios from "axios"
import useFetchData from "./useFetchData"
const themes = {
light: {
foreground: "#000000",
background: "#eeeeee",
},
dark: {
foreground: "#ffffff",
background: "#222222",
},
red: {
foreground: "#ffffff",
background: "#ff0000",
},
}
function A() {
return <B theme={themes.red} />
}
function B(props) {
return (
<div>
<C theme={props.theme} />
</div>
)
}
function C(props) {
return (
<div>
<button
style={{
color: props.theme.foreground,
backgroundColor: props.theme.background,
margin: "20px",
}}>
A传B,B给C,C改变主题
</button>
</div>
)
}
export default A
// 改用useContext
import { useContext, createContext } from "react"
// 问题:theme在A组件,要穿透BC组件在C中使用
const themes = {
light: {
foreground: "#000000",
background: "#eeeeee",
},
dark: {
foreground: "#ffffff",
background: "#222222",
},
red: {
foreground: "#ffffff",
background: "#ff0000",
},
}
const ThemeContext = createContext(themes.light)
function A() {
return (
<ThemeContext.Provider value={themes.red}>
<B />
</ThemeContext.Provider>
)
}
function B() {
return (
<div>
<C />
</div>
)
}
function C() {
const theme = useContext(ThemeContext)
return (
<div>
<button
style={{
color: theme.foreground,
backgroundColor: theme.background,
}}>
A直接传C
</button>
</div>
)
}
export default A
memo
// 出现的问题:点击按钮增加count的同时,也会打印Child
// 解决:React.memo
import { useState, useEffect, memo } from "react"
// 写法一:
// const Child = memo(() => {
// console.log("Child")
// return <div>Child</div>
// })
// 写法二:
let Child = function () {
console.log("Child")
return <div>Child</div>
}
Child = memo(Child)
function Parent() {
const [count, setCount] = useState(0)
return (
<div>
<p>当前count:{count}</p>
<button onClick={() => setCount(count + 1)}>增加</button>
<Child />
</div>
)
}
export default Parent
useCallback
// 问题:子组件没有变化,但是点击父组件按钮时会重新渲染Child
// ∵父组件点击按钮时,还会重新创建handleClick
// 解决:useCallback
import { useState, useCallback, memo } from "react"
let Child = function (props) {
console.log("Child")
const { handleClick, title } = props
return (
<div>
<p>{title}</p>
<button onClick={handleClick}>{title}</button>
</div>
)
}
Child = memo(Child)
function Parent() {
const [count, setCount] = useState(0)
const handleClick = useCallback(() => {
console.log("子组件被点击了")
}, [])
return (
<div>
<p>当前count:{count}</p>
<button onClick={() => setCount(count + 1)}>增加</button>
<Child handleClick={handleClick} title={"子组件"} />
</div>
)
}
export default Parent
useMemo
// 问题:把传递的title改为对象类型的data,再去点击按钮,子组件又重新渲染了
// 解决:useMemo
import { useState, useCallback, memo, useMemo } from "react"
let Child = function (props) {
console.log("Child")
const { handleClick, data } = props
return (
<div>
<p>{data.title}</p>
<button onClick={handleClick}>点击子组件</button>
</div>
)
}
Child = memo(Child)
function Parent() {
const [count, setCount] = useState(0)
const handleClick = useCallback(() => {
console.log("子组件被点击了")
}, [])
const data = useMemo(
() => ({
title: "这里是子组件",
}),
[]
)
return (
<div>
<p>当前count:{count}</p>
<button onClick={() => setCount(count + 1)}>增加</button>
<Child handleClick={handleClick} data={data} />
</div>
)
}
export default Parent
// 计算属性:useMemo的第二个参数:依赖项
// 只有依赖项发生改变,才会重新计算,可以当作Vue计算属性
const fullName = useMemo(() => {
return lastName + firstName
}, [lastName, firstName])
// memo,useMemo,useCallback的区别
// memo:缓存子组件
// useMemo:对值进行包裹,只有依赖项发生改变,才会重新计算
// useCallback:对函数进行包裹,只有依赖项发生改变,才会重新返回一个新的函数
// useCallback(fn,deps)相当于useMemo(()=>fn,deps)
路由router
安装、基础使用
npm i history@5 react-router-dom@6
// index.js入口
import React from "react"
import ReactDOM from "react-dom/client"
import App from "./App"
import reportWebVitals from "./reportWebVitals"
import { BrowserRouter } from "react-router-dom"
import "./index.css"
const root = ReactDOM.createRoot(document.getElementById("root"))
root.render(
<React.StrictMode>
<BrowserRouter>
<App />
</BrowserRouter>
</React.StrictMode>
)
reportWebVitals()
import * as React from "react"
import { useState } from "react"
import useFetchData from "./useFetchData"
import { Routes, Route, Link } from "react-router-dom"
function App() {
return (
<div>
<header>
<h1>React Hooks</h1>
</header>
<Routes>
<Route path="/" element={<Home />} />
<Route path="/about" element={<About />} />
</Routes>
</div>
)
}
function Home() {
return (
<>
<h2>Home</h2>
<Link to="/about">About</Link>
</>
)
}
function About() {
return (
<>
<h2>About</h2>
<Link to="/">Home</Link>
</>
)
}
export default App
使用组件
// 增加一些路由
// src/routes/invo.jsx,expen.jsx
export default function Invo() {
return (
<main style={{ border: "2px solid gold" }}>
<h2>Invo</h2>
</main>
)
}
export default function Expen() {
return (
<main style={{ border: "2px solid green" }}>
<h2>Expen</h2>
</main>
)
}
// app.js中
import * as React from "react"
import { useState } from "react"
import useFetchData from "./useFetchData"
import { Routes, Route, Link } from "react-router-dom"
function App() {
return (
<div>
<h1>React Hooks</h1>
<nav style={{ border: "2px solid red" }}>
<Link to="/invo">invo</Link>|{""}
<Link to="/expen">expen</Link>
</nav>
</div>
)
}
export default App
// index.js
import React from "react"
import ReactDOM from "react-dom/client"
import reportWebVitals from "./reportWebVitals"
import { BrowserRouter, Routes, Route } from "react-router-dom"
import "./index.css"
import App from "./App"
import Invo from "./routes/invo.jsx"
import Expen from "./routes/expen.jsx"
const rootElement = document.getElementById("root")
const root = ReactDOM.createRoot(rootElement)
root.render(
<React.StrictMode>
<BrowserRouter>
<Routes>
<Route path="/" element={<App />} />
<Route path="/Invo" element={<Invo />} />
<Route path="/Expen" element={<Expen />} />
</Routes>
</BrowserRouter>
</React.StrictMode>
)
reportWebVitals()
嵌套路由
// 修改index.js的文件结构
import React from "react"
import ReactDOM from "react-dom/client"
import reportWebVitals from "./reportWebVitals"
import { BrowserRouter, Routes, Route } from "react-router-dom"
import "./index.css"
import App from "./App"
import Invo from "./routes/invo.jsx"
import Expen from "./routes/expen.jsx"
const rootElement = document.getElementById("root")
const root = ReactDOM.createRoot(rootElement)
root.render(
<React.StrictMode>
<BrowserRouter>
<Routes>
<Route path="/" element={<App />}>
<Route path="Invo" element={<Invo />} />
<Route path="Expen" element={<Expen />} />
</Route>
</Routes>
</BrowserRouter>
</React.StrictMode>
)
reportWebVitals()
// app.js中添加oulet
import * as React from "react"
import { useState } from "react"
import useFetchData from "./useFetchData"
import { Routes, Route, Link, Outlet } from "react-router-dom"
function App() {
return (
<div>
<h1>React Hooks</h1>
<nav style={{ border: "2px solid red" }}>
<Link to="/invo">invo</Link>|{""}
<Link to="/expen">expen</Link>
</nav>
<Outlet />
</div>
)
}
export default App
链接列表
// 新建data.js
let invo = [
{ name: "a", number: "1000", amount: 0, due: "2023-01-01" },
{ name: "b", number: "1001", amount: 0, due: "2023-01-02" },
{ name: "c", number: "1002", amount: 0, due: "2023-01-03" },
{ name: "d", number: "1003", amount: 0, due: "2023-01-04" },
{ name: "e", number: "1004", amount: 0, due: "2023-01-05" },
]
export function getInvo() {
return invo
}
// invo.js
import { getInvo } from "../data"
import { Link } from "react-router-dom"
export default function Invo() {
let invoives = getInvo()
return (
<div style={{ border: "20px solid gold" }}>
<nav>
{invoives.map((invo) => (
<Link key={invo.number} to={`/invoices/${invo.number}`} style={{ display: "block", margin: "1rem" }}>
{invo.name}
</Link>
))}
</nav>
<h2>Invo</h2>
</div>
)
}
未匹配的路由
// index.js
const rootElement = document.getElementById("root")
const root = ReactDOM.createRoot(rootElement)
root.render(
<React.StrictMode>
<BrowserRouter>
<Routes>
<Route path="/" element={<App />}>
<Route path="invo" element={<Invo />} />
<Route path="Expen" element={<Expen />} />
<Route path="*" element={<div>404 Not Found</div>} />
</Route>
</Routes>
</BrowserRouter>
</React.StrictMode>
)
获取URL参数
// invo中添加in_vo子路由
root.render(
<React.StrictMode>
<BrowserRouter>
<Routes>
<Route path="/" element={<App />}>
<Route path="invo" element={<Invo />}>
<Route path=":invoId" element={<In_vo />} />
</Route>
<Route path="Expen" element={<Expen />} />
<Route path="*" element={<div>404 Not Found</div>} />
</Route>
</Routes>
</BrowserRouter>
</React.StrictMode>
)
// 新建in_vo.js
// 作为invo的子路由,传参进去
import { useParams } from "react-router-dom"
export default function In_vo() {
let params = useParams()
let invoId = params.invoId
return (
<div>
<h2>Invo :{invoId}</h2>
</div>
)
}
// invo.jsx
export default function Invo() {
let invoives = getInvo()
return (
<div style={{ border: "20px solid gold" }}>
<nav>
{invoives.map((invo) => (
<Link key={invo.number} to={`/invo/${invo.number}`} style={{ display: "block", margin: "1rem" }}>
{invo.name}
</Link>
))}
</nav>
<Outlet />
</div>
)
}
index路由
问题:点击ivo导航时没有invoId匹配不到对应的路由,使用对应路由
root.render(
<React.StrictMode>
<BrowserRouter>
<Routes>
<Route path="/" element={<App />}>
<Route path="invo" element={<Invo />}>
{/* 问题:点击ivo导航时没有invoId匹配不到对应的路由,使用对应路由 */}
<Route index element={<div>invo</div>} />
<Route path=":invoId" element={<In_vo />} />
</Route>
<Route path="Expen" element={<Expen />} />
<Route path="*" element={<div>404 Not Found</div>} />
</Route>
</Routes>
</BrowserRouter>
</React.StrictMode>
)
激活的链接
import { getInvo } from "../data"
import { Link, Outlet, NavLink } from "react-router-dom"
export default function Invo() {
let invoives = getInvo()
return (
<div style={{ border: "20px solid gold" }}>
<nav>
{invoives.map((invo) => (
// link改为NavLink
// 使用style中的isActive
<NavLink
key={invo.number}
to={`/invo/${invo.number}`}
style={({ isActive }) => {
return { display: "block", margin: "1rem", color: isActive ? "red" : "black" }
}}>
{invo.name}
</NavLink>
))}
</nav>
y
<Outlet />
</div>
)
}
url搜索参数
// invo.jsx
import { getInvo } from "../data"
import { Link, Outlet, NavLink, useSearchParams } from "react-router-dom"
// invo/1004?filter=3
// 使用useSearchParams,使用方法与useState类似
export default function Invo() {
let invoives = getInvo()
let [searchParams, setSearchParams] = useSearchParams()
return (
<div style={{ border: "20px solid gold" }}>
<nav>
{useSearchParams}
<input
value={searchParams.get("filter") || ""}
onChange={(e) => {
let filter = e.target.value
if (filter) {
setSearchParams({ filter })
} else {
setSearchParams({})
}
}}
/>
{invoives
.filter((invo) => {
let filter = searchParams.get("filter")
if (!filter) return true
let name = invo.name.toLowerCase()
return name.startsWith(filter)
})
.map((invo) => (
<NavLink
key={invo.number}
to={`/invo/${invo.number}`}
style={({ isActive }) => {
return { display: "block", margin: "1rem", color: isActive ? "red" : "black" }
}}>
{invo.name}
</NavLink>
))}
</nav>
y
<Outlet />
</div>
)
}
自定义行为
import { getInvo } from "../data"
import { Link, Outlet, NavLink, useSearchParams, useLocation } from "react-router-dom"
// 问题:输入框输入值,搜索列表,然后点击链接,搜索值会被清除
// 怎么保持?filter=a始终存在?
// useLocation + QueryNavLink
function QueryNavLink({ to, ...props }) {
let location = useLocation()
return <NavLink to={to + location.search} {...props}></NavLink>
}
export default function Invo() {
let invoives = getInvo()
let [searchParams, setSearchParams] = useSearchParams()
return (
<div style={{ border: "20px solid gold" }}>
<nav>
{useSearchParams}
<input
value={searchParams.get("filter") || ""}
onChange={(e) => {
let filter = e.target.value
if (filter) {
setSearchParams({ filter })
} else {
setSearchParams({})
}
}}
/>
{invoives
.filter((invo) => {
let filter = searchParams.get("filter")
if (!filter) return true
let name = invo.name.toLowerCase()
return name.startsWith(filter)
})
.map((invo) => (
<QueryNavLink
key={invo.number}
to={`/invo/${invo.number}`}
style={({ isActive }) => {
return { display: "block", margin: "1rem", color: isActive ? "red" : "black" }
}}>
{invo.name}
</QueryNavLink>
))}
</nav>
y
<Outlet />
</div>
)
}
使用代码跳转
// in_vo.js中
import { useParams, useSearchParams, useNavigate } from "react-router-dom"
import { getInvo, deleteInvo } from "../data"
// 手动跳转用navigate('/invo')
export default function In_vo() {
let params = useParams()
let navigate = useNavigate()
let invoInfo = getInvo(parseInt(params.invoId, 10))
return (
<div>
<p> name:{invoInfo[0].name}</p>
<p> due:{invoInfo[0].due}</p>
<p>
<button
onClick={() => {
// deleteInvo(invoInfo[0].number)
navigate("/invo")
}}>
删除
</button>
</p>
</div>
)
}
案例:基础使用、布局模板
// index.js
import React from "react"
import ReactDOM from "react-dom/client"
import { BrowserRouter, Routes, Route } from "react-router-dom"
import "./index.css"
import App from "./App"
import Invo from "./routes/invo.jsx"
import In_vo from "./routes/in_vo.js"
import Expen from "./routes/expen.jsx"
import reportWebVitals from "./reportWebVitals"
const rootElement = document.getElementById("root")
const root = ReactDOM.createRoot(rootElement)
root.render(
<React.StrictMode>
<BrowserRouter>
<App />
</BrowserRouter>
</React.StrictMode>
)
reportWebVitals()
// app.js
import * as React from "react"
import { useState } from "react"
import useFetchData from "./useFetchData"
import { Routes, Route, Link, Outlet } from "react-router-dom"
function App() {
return (
<div>
<h1>基础实例</h1>
<p>此示例展示了如何使用 react-router-dom 来实现基本的路由功能。</p>
<Routes>
<Route path="/" element={<Layout />}>
<Route index element={<Home />} />
<Route path="about" element={<About />} />
<Route path="dashboard" element={<Dashboard />} />
<Route path="*" element={<NotFound />} />
</Route>
</Routes>
<Outlet />
</div>
)
}
function Layout() {
return (
<div>
<nav>
<ul>
<li>
<Link to="/">首页</Link>
</li>
<li>
<Link to="/about">关于</Link>
</li>
<li>
<Link to="/dashboard">仪表盘</Link>
</li>
<li>
<Link to="/notfound">404</Link>
</li>
</ul>
</nav>
<hr />
<Outlet />
</div>
)
}
function Home() {
return <div>Home</div>
}
function About() {
return <div>About</div>
}
function NotFound() {
return (
<div>
404 Not Found-aaa
<p>
<Link to="/">返回首页</Link>
</p>
</div>
)
}
function Dashboard() {
return <div>Dashboard</div>
}
export default App
案例:用户认证
案例:懒加载
ant design
分为容器属性、项目属性
容器属性
1. grid-template-rows/grid-template-columns
行/列
取值:10px\1fr,2fr\10%\auto
a,fr
相对长度
b,repeat()
分行分列较多时使用
2. grid-gap/grid-row-gap/grid-column-gap
间隔(可用,已弃用)
取值:10px
3. grid-auto-flow
排列顺序
默认row先行后列/column先列后行
4. grid-template-areas/grid-areas
分区,合并
5. justify-items/align-items/place-items
单元格的水平位置/垂直位置/垂直-水平的复合
默认stretch拉伸/start/end/center
6. justify-content/align-content/place-content
单元格整体对容器的水平位置/垂直位置/垂直-水平的复合
默认stretch/start/end/center/space-between两端对齐/space-around环绕/spance-evenly均分
项目属性
1. grid-column-start/grid-column-end/grid-row-start/grid-row-end/
grid-column/grid-row/grid-area
位置
写法一:行线列线
grid-row-start: 2;
grid-row-end: 4;
grid-column-start: 2;
grid-column-end: 3;
写法二:span占据的单元格个数
grid-row-start: 3;
grid-row-end: span 3;
写法三:grid-row/grid-column简写
grid-row: 3/6;
grid-column: 2/4;
或
grid-row: 3/6;
grid-column: 2/span 2;
写法四:grid-area简写
用法奇怪,可以不用,纵向起点/横向起点/纵向终点/横向终点
grid-area: 3/2/6/4;
2. justify-self/align-self/place-self
单独的justify-items,align-items,place-items
默认stretch/start/end/center