REACT实战

178 阅读12分钟

基础

安装

<!-- 创建项目 -->
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

191