React学习笔记

目录

一、开发环境搭建

1、创建React项目

2、项目目录及文件简介

3、安装React Developer Tools

二、核心语法

2.1 JSX

2.2 条件渲染

2.3 列表渲染

2.4 事件绑定

2.5 样式控制

2.6 常用 React Hook

 2.6.1 useState

 2.6.2 useRef

 2.6.3 useEffect

2.7 性能优化

2.7.1 useReducer

2.7.2 useMemo

2.7.3 React.memo

2.7.4 useCallback

2.8 封装自定义Hook

三、组件

3.1 概念

3.2 父子通信

3.3 子父通信

3.4 跨层级组件通信

3.5 Class组件 

四、集中状态管理

4.1 Redux

4.2 zustand

五、Router

5.1 基本使用

5.2 嵌套路由 

5.3 路由导航 

5.4 路由模式

5.5 路由懒加载

5.6 路由权限控制 

 六、React+TypeScript

6.1 useState

6.2 props

七、免责声明


一、开发环境搭建

1、创建React项目

① 使用 create-react-app 官方脚手架创建项目

执行cmd命令:npx create-react-app xxx

xxx为项目名称(英文,小写)

create-react-app 底层由Webpack构建

② 使用 vite 创建: npm create vite@latest 选择react即可

③ 其他创建方式:启动一个新的 React 项目 – React 中文文档

2、项目目录及文件简介

使用 create-react-app创建:

│  .gitignore
│  package-lock.json
│  package.json
│  README.md
│  
├─node_modules // 存放项目所依赖的一些第三方包文件
├─public // 静态资源文件夹
│      favicon.ico // 导航图标
│      index.html // 项目首页的html模版
│      logo192.png // logo图片
│      logo512.png // logo图片
│      manifest.json // 应用加壳配置文件,在手机上描述我们应用程序的json文件
│      robots.txt // 爬虫协议文件
│      
└─src // 源码文件夹
        App.css //App组件的样式
        App.js // App组件
        App.test.js // 自动化测试文件,用于给App做测试
        index.css // 全局样式
        index.js // 入口文件,所用组件都会通过index.js载入
        logo.svg // logo
        reportWebVitals.js // 导入了一个web-vitals的第三方库,用来检查应用程序的性能
        setupTests.js // 从Jest-dom导入匹配器,在进行测试时使用

3、安装React Developer Tools

React Developer Tools用于react项目的调试

二、核心语法

2.1 JSX

概念:

        JSX是JavaScript和XML(HTML)的缩写,表示在JS代码中编写HTML模版结构,它是React中编写UI模版的方式;

        JSX并不是标准的JS语法,它是JS的语法扩展,浏览器本身不能识别,需要通过解析工具(babel)做解析之后才能在浏览器中运行。

示例:

import logo from './logo.svg';
import './App.css';

function App() {
  return (
    <div className="App">
      <header className="App-header">
        <img src={logo} className="App-logo" alt="logo" />
        <p>
          Edit <code>src/App.js</code> and save to reload.
        </p>
        <a
          className="App-link"
          href="https://reactjs.org"
          target="_blank"
          rel="noopener noreferrer"
        >
          Learn React
        </a>
      </header>
    </div>
  );
}

export default App;

JSX规则: 

        ① 只能返回一个根元素,可以用 <>  </> 元素来代替根元素;

        ② 标签必须闭合;

        ③ 使用驼峰式命名法给 大部分属性命名;由于 class 是一个保留字,所以在 React 中需要用 className 来代替;

        ④ 在JSX中可以通过 大括号语法{} 识别 JavaScript中的表达式,比如常见的变量、函数调用、方法调用等等。

        注意:if语句、switch语句、变量声明属于语句,不是表达式,不能出现在{}中;标签的属性值绑定直接使用{},如src={....},错误写法src=“{....}”。

2.2 条件渲染

① 与运算符(&&)及 三元表达式(? :)

function App() {
  const flag = false;

  return (
    <>
      {/* 与运算符&& */}
      {flag && <div>flag为true时显示</div>}
      {/* 三元表达式(?:) */}
      {flag ? <div>flag为true时显示</div> : <div>flag为false时显示</div>}
    </>
  );
}

export default App;

② if语句:

function App() {
  const id = 1;
  function hobby() {
    if (id === 0) {
      return <div>唱</div>;
    } else if (id === 1) {
      return <div>跳</div>;
    } else {
      return <div>rap</div>;
    }
  }
  return (
    <>
      {hobby()}
    </>
  );
}

export default App;

2.3 列表渲染

① map:

import { Fragment } from "react";

function App() {
  const hobbys = [
    { id: 0, hobby: "唱" },
    { id: 1, hobby: "跳" },
    { id: 2, hobby: "rap" },
    { id: 3, hobby: "篮球" }
  ];

  const listHobbys = hobbys.map( item => (
    <Fragment key={item.id}>
      <li>{item.hobby}</li>
      <li>--分割线--</li>
    </Fragment>
  ));
  return (
    <>
      <ul>{listHobbys}</ul>
    </>
  );
}

export default App;

Tip:Fragment 标签相当于  <>  </> 标签,与之不同的是Fragment标签支持能接受键值或属性,可以遍历循环渲染元素

② filter:筛选数组,使用方法与map类似

2.4 事件绑定

语法:on + 事件名称 = { 事件处理程序 },整体上遵循驼峰命名法

以点击事件为例:

① 基础回调:

function App() {
  // -----基础回调
  const handlerClick = (e) => {
    console.log("button被点击了");
    // 获取事件参数e
    console.log(e);
  };

  return (
    <>
      <button onClick={handlerClick}>click me</button>
    </>
  );
}
export default App;

② 传递自定义参数

function App() {
  //------传递自定义参数
  const handlerClick = (name) => {
    console.log(name);
  };

  return (
    <>
      <button onClick={() => handlerClick("jack")}>click me</button>
    </>
  );
}
export default App;

③ 传递自定义参数+事件参数e

function App() {
  //------传递自定义参数+事件参数e
  const handlerClick = (name, e) => {
    console.log(name, e);
  };
  return (
    <>
      <button onClick={(e) => handlerClick("jack", e)}>click me</button>
    </>
  );
}
export default App;

注意:传递自定义参数必须使用箭头函数的形式。

2.5 样式控制

行内样式控制及类名样式控制:

import "./index.css"; // 导入外部样式
function App() {
  return (
    <div>
      {/* 行内样式控制,style属性传入对象 */}
      <span style={{ color: "red", fontSize: "50px" }}>this is span</span>
      {/* 通过class类名控制 */}
      <span className="foo">this is span</span>
    </div>
  );
}
export default App;

 ② 设置属性集中对象

function App() {
    // 属性集中在一个对象中
  const imagePropsData = {
    className: "foo",
    style: {
      width: "200px",
      height: "200px"
    },
    src: "http//......"
  };
  return (
    <div>
      <image {...imagePropsData} title=''/>
    </div>
  );
}
export default App;

2.6 常用 React Hook

 2.6.1 useState

useState 是一个 React Hook(函数),它允许我们向组件添加一个状态变量, 状态变量一旦发生变化组件的视图UI也会跟着变化。

语法:const [state, setState] = useState( initialState )

  • state:状态变量
  • setState:修改状态变量值的函数(异步)
  • initialState:状态变量初始值

使用方法:

① 状态变量为 简单数据 类型

import { useState } from "react";
function App() {
  // 调用 useState 添加一个状态变量
  const [count, setCount] = useState(0);
  const handlerClick = () => {
    // 使用传入的新值修改count;重新使用新的count渲染ui
    setCount(count + 1);
  };

  return (
    <div>
      <button onClick={handlerClick}>{count}</button>
    </div>
  );
}
export default App;

② 状态变量为 对象 类型

import { useState } from "react";
function App() {
  const [form, setForm] = useState({ name: "jack" });
  const changeForm = () => {
    setForm({
      ...form,
      name: "john"
    // 变更的属性后写
    });
  };
  return (
    <div>
      <button onClick={changeForm}>{form.name}</button>
    </div>
  );
}
export default App;

提示:在React中,状态被认为是只读的,我们应该始终替换它而不是修改它对于对象类型的状态变量,应该始终传给set方法一个全新的对象来进行修改。

案例:绑定受控表单:

核心绑定流程:

① 声明一个react状态-useState
② 通过value属性值绑定react状态
③ 绑定onChange事件 通过事件参数e拿到输入框最新的值 反向修改到react状态

import { useState } from "react";

function App() {
  const [value, setValue] = useState("");
  return (
    <>
      <input type="text" value={value} onChange={(e) => setValue(e.target.value)} />
    </>
  );
}

export default App;

 2.6.2 useRef

作用:获取dom

① useRef生成ref对象,绑定到dom标签上

② dom可用时,ref.current获取dom

③ 渲染完毕之后dom生成之后才可用

import { useRef } from "react"; 
function App() {
  const inputRef = useRef(null);
  const showDom = () => {
    console.dir(inputRef.current);
    inputRef.current.focus(); // 获取焦点
  };
  return (
    <>
      <input type="text" ref={inputRef} />
      <button onClick={showDom}>获取dom</button>
    </>
  );
}

export default App;

使用场景:父组件调用子组件的方法 

import { useRef, forwardRef, useImperativeHandle } from "react";

// 子组件
const Child = forwardRef((props, ref) => {
  const childFn = () => {
    console.log("子组件childFn方法");
  };
  // 暴露给父组件的方法,属性等
  useImperativeHandle(ref, () => ({ childFn }));

  return (
    <>
      <div>子组件</div>
    </>
  );
});

// 父组件
function App() {
  // 获取子组件
  const childRef = useRef();
  const handleClick = () => {
    // 使用子组件暴露的方法
    childRef.current.childFn();
  };
  return (
    <div>
      <Child ref={childRef} />
      <button onClick={handleClick}>点击执行子组件方法</button>
    </div>
  );
}

export default App;

 2.6.3 useEffect

概念useEffect是一个React Hook函数,用于在React组件中创建不是由事件引起而是由渲染本身引起的操作(副作用), 即执行时机为渲染时触发;使用场景如发送AJAX请求,更改DOM等等。

语法useEffect( setup, dependencies? )

        参数 setup 是一个函数,可以把它叫做副作用函数,在函数内部可以放置要执行的操作;

        参数 dependencies 是一个数组(可选参),在数组里放置依赖项,不同依赖项会影响第一个参数函数的执行,当是一个空数组的时候,副作用函数只会在组件渲染完毕之后执行一次。

依赖项
副作用函数执行时机
没有依赖项
组件初始渲染 + 组件更新时执行
空数组依赖
只在初始渲染时执行一次
添加特定依赖项
组件初始渲染 + 特性依赖项变化时执行
基本使用:
import { useEffect, useState } from "react";
// useEffect 依赖项参数说明
function App() {
  // 1、无依赖项   副作用函数执行时机:初始渲染+组件更新
  const [count, setCount] = useState(0);
  useEffect(() => {
    console.log("无依赖项:副作用函数执行了");
  });

  // 2、空数组依赖  副作用函数执行时机:初始渲染
  useEffect(() => {
    console.log("空数组依赖:副作用函数执行了");
  }, []);

  // 3、添加特定依赖项  副作用函数执行时机:初始渲染+依赖项变化执行
  useEffect(() => {
    // count发生变化时执行
    console.log("添加特定依赖项:副作用函数执行了");
  }, [count]);

  return (
    <>
      <div>this is app</div>
      <button onClick={() => setCount(count + 1)}>点击组件更新:{count}</button>
    </>
  );
}

export default App;

清除副作用:

场景:在useEffect中开启了一个定时器,我们想在组件卸载时把这个定时器再清理掉。

语法:

useEffect(() => {
  //  实现操作副作用逻辑
    return () => {
      // 清除副作用逻辑
    };
  }, []);

案例:需求:在Son组件渲染时开启一个定制器,卸载时清除这个定时器

import { useEffect, useState } from "react";

function Son() {

  //渲染时开启一个定时器
  useEffect(() => {
    const timer = setInterval(() => console.log("定时器执行中..."), 1000);
    return () => {
      // 清除副作用函数
      clearInterval(timer);
    };
  }, []);

  return <>this is son</>;
}
function App() {
  // 通过条件渲染模仿组件卸载
  const [show, setShow] = useState(true);
  return (
    <>
      {show && <Son />}
      <button onClick={() => setShow(true)}>加载son组件</button>
      <button onClick={() => setShow(false)}>卸载son组件</button>
    </>
  );
}

export default App;

2.7 性能优化

2.7.1 useReducer

作用:和useState的作用类似,用来管理相对复杂的状态数据

基础用法:

  1. 定义一个reducer函数(根据不同的action返回不同的新状态)
  2. 在组件中调用useReducer,并传入reducer函数和状态的初始值
  3. 事件发生时,通过dispatch函数分派一个action对象(通知reducer要返回哪个新状态并渲染UI)
import { useReducer } from "react"

// 1. 定义reducer函数 根据不同的action 返回不同的状态
function reducer (state, action) {
  switch (action.type) {
    case 'INC':
      return state + 1
    case 'DEC':
      return state - 1
    case 'SET':
      return action.payload
    default:
      return state
  }
}

function App() {
  // 2. 组件中调用useReducer(reducer, 0) => [state, dispatch]
  const [state, dispatch] = useReducer(reducer, 0)
  return (
    <div className="App">
      this is app
      {/* 3. 调用dispatch({type:'INC'}) => 通知reducer产生一个新的状态 使用这个新状态更新UI */}
      <button onClick={() => dispatch({ type: 'DEC' })}>-</button>
      {state}
      <button onClick={() => dispatch({ type: 'INC' })}>+</button>
      <button onClick={() => dispatch({ type: 'SET', payload: 100 })}>update</button>
    </div>
  )
}

export default App

2.7.2 useMemo

作用:在组件每次重新渲染的时候缓存计算的结果,类似vue的计算属性,适用场景:消耗非常大的计算。

与普通计算区别:普通计算会随着UI刷新重新计算,useMemo只会随着依赖项的改变而变化。

语法:useMemo(() => { 根据依赖项a返回的计算结果 },[ 依赖项a ]}

说明:使用useMemo做缓存之后可以保证只有依赖项发生变化时才会重新计算。

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 [count1, setCount1] = useState(0)


  const result = useMemo(() => {
    // 返回计算得到的结果
    return fib(count1)
  }, [count1])


  const [count2, setCount2] = useState(0)
  console.log('组件重新渲染了')
  return (
    <div className="App">
      this is app
      <button onClick={() => setCount1(count1 + 1)}>change count1: {count1}</button>
      <button onClick={() => setCount2(count2 + 1)}>change count2: {count2}</button>
      {result}
    </div>
  )
}

2.7.3 React.memo

作用:允许组件在Props没有改变的情况下跳过渲染。

React组件默认的渲染机制:只要父组件重新渲染子组件就会重新渲染。

memo进行缓存:只有props发生变化的时候才会重新渲染 (不考虑context)

语法:使用 memo 将 子组件 包裹,并用变量接收,用此变量作为 子组件 名字。

const MemoComponent = memo(function SomeComponent (props) {
  // ...
})

props的比较机制:在使用memo缓存组件之后,React会对每一个 prop 使用 Object.is 比较新值和老值,返回true,表示没有变化。

        prop是简单类型:Object.is(3, 3) => true 没有变化

        prop是引用类型(对象 / 数组):Object([], []) => false 有变化,React只关心引用是否变化

使用方法:

import { memo, useState } from "react"

const MemoSon = memo(function Son () {
  console.log('我是子组件,我重新渲染了')
  return <div>this is son</div>
})

function App () {
  const [count, setCount] = useState(0)
  return (
    <div className="App">
       // 点击按钮父组件会刷新,子组件不刷新,
       // 也就是说`console.log('我是子组件,我重新渲染了')` 不会再次执行
      <button onClick={() => setCount(count + 1)}>+{count}</button>
      <MemoSon />
    </div>
  )
}

export default App

2.7.4 useCallback

​​​作用:在组件多次重新渲染的时候缓存函数,跳过组件的重新渲染

语法:useCallback(fn,dependence)

与useMemo区别:useMemo缓存的是数据,useCallback缓存的是函数

使用方法:

        ① 首先将子组件变成一个记忆组件:参考 2.7.3 React.memo 

        ② 将需要缓存的函数使用useCallback包裹

import { memo, useCallback, useState } from "react"


const Input = memo(function Input ({ onChange }) {
  console.log('子组件重新渲染了')
  return <input type="text" onChange={(e) => onChange(e.target.value)} />
})

function App () {
  // 传给子组件的函数
  const changeHandler = useCallback((value) => console.log(value), [])
  // 触发父组件重新渲染的函数
  const [count, setCount] = useState(0)
  return (
    <div className="App">
      {/* 把函数作为prop传给子组件 */}
      <Input onChange={changeHandler} />
      <button onClick={() => setCount(count + 1)}>{count}</button>
    </div>
  )
}

export default App

2.8 封装自定义Hook

概念:自定义Hook是以 use 打头的函数,通过自定义Hook函数可以用来实现逻辑的封装和复用(组件间共享逻辑)。

使用规则:只能在组件中或者其他自定义Hook函数中调用;只能在组件的顶层调用,不能嵌套在 if、for、其他函数中。

案例:封装组件切换的Hook

import { useState } from "react";
// 封装自定义hook
function useToggle() {
  const [value, setValue] = useState(true);
  const toggle = () => setValue(!value);
  return { value, toggle };
}

// 在组件中使用
function App() {
  const { value, toggle } = useToggle();
  return (
    <div>
      {value && <div>this is div</div>}
      <button onClick={toggle}>toggle</button>
    </div>
  );
}

export default App;

三、组件

3.1 概念

 React组件分为函数式组件与类组件,官方主推函数式组件。

① 定义组件:

// 定义组件并导出 Button.js
export default function Button() {
    // 编写组件逻辑
  return (
    <>
      <button>我是自定义组件</button>
    </>
  );
}

② 使用组件:

// 导入并使用组件 App.js
import Button from "./Button";
function App() {
  return (
    <div className="App">
      <Button />
    </div>
  );
}

export default App;

注意:组件名字以大写字母开头。

3.2 父子通信

props可传递任意的数据:数字、字符串、布尔值、数组、对象、函数、JSX;

props是只读对象,子组件只能读取props中的数据,不能直接进行修改, 父组件的数据只能由父组件修改。

// 子组件
function Son(props) {
  return (
    <>
      <button>{props.name}</button>
      {props.jsx}
    </>
  );
}

// 父子件
function App() {
  const msg = {
    name: "son",
    // 传递 JSX
    jsx: <div>666</div>
  };
  return (
    <div>
      <Son {...msg} />
    </div>
  );
}

export default App;

插槽:children特殊的prop

// 子组件
function Son({ children }) {
  return <div>{children}</div>;
}

// 父组件
function App() {
  return (
    <>
      <Son>
        <span>this is son2 span</span>
      </Son>
    </>
  );
}

export default App;

3.3 子父通信

核心思路:在子组件中调用父组件中的函数并传递参数。
① 父组件 定义以形参接收数据的函数 通过 子传父 将 函数 传递给子组件
② 子组件 接收父组件传递的函数 并执行函数传参
import { useState } from "react";

// 子组件
function Son({ onGetSonMsg }) {
  // 子组件传给父组件的数据
  const sonMsg = "this is son msg";
  return (
    <div>
      <button onClick={() => onGetSonMsg(sonMsg)}>sendMsg</button>
    </div>
  );
}

// 父组件
function App() {
  const [msg, setMsg] = useState("");
  // 传递给子组件的函数
  const getMsg = (msg) => {
    setMsg(msg);
  };
  return (
    <>
      <Son onGetSonMsg={getMsg} />
      {msg}
    </>
  );
}

export default App;

3.4 跨层级组件通信

实现步骤:

  1. 使用 createContext 方法创建一个上下文对象Ctx
  2. 在顶层组件(App)中通过 Ctx.Provider 组件 提供数据
  3. 在底层组件(B)中通过 useContext 钩子函数获取消费数据

eg:组件层级:App → A → B

import { createContext, useContext } from "react";

const MsgContext = createContext(); // 步骤1

// 顶层组件
function App() {
  const msg = "this is App mag";
  return (
    <>
      {/* 步骤2 */}
      <MsgContext.Provider value={msg}>
        <A />
      </MsgContext.Provider>
    </>
  );
}

// 底层组件
function B() {
  // 步骤3
  const msg = useContext(MsgContext);
  return <div>this is B{msg}</div>;
}

// 中间组件
function A({ onGetAMsg }) {
  return (
    <div>
      this is A
      <B />
    </div>
  );
}
export default App;

3.5 Class组件 

 现开发不推荐使用class组件。

3.5.1 class组件的基本结构

import { Component } from "react"

class Counter extends Component {
  // 1. 通过类属性state定义状态数据
  state = {
    count: 0
  }

  // 2. 定义事件回调修改状态数据
  setCount = () => {
    // 通过setState方法来修改状态数据
    this.setState({
      count: this.state.count + 1
    })
  }

  // 3. 通过render来写UI模版
  render () {
    return <button onClick={this.setCount}>{this.state.count}</button>
  }
}

function App () {
  return (
    <>
      <Counter />
    </>
  )
}

export default App

3.5.2   class组件的生命周期

        componentDidMount:组件挂载完毕自动执行

        componentWillUnmount:组件卸载时自动执行

import { Component, useState } from "react"

class Son extends Component {
  // 声明周期函数
  // 组件渲染完毕执行一次  发送网络请求
  componentDidMount () {
    console.log('组件渲染完毕了,请求发送起来')
    // 开启定时器
    this.timer = setInterval(() => {
      console.log('定时器运行中')
    }, 1000)
  }

  // 组件卸载的时候自动执行  副作用清理的工作 清除定时器 清除事件绑定
  componentWillUnmount () {
    console.log('组件son被卸载了')
    // 清除定时器
    clearInterval(this.timer)
  }

  render () {
    return <div>i am Son</div>
  }
}

function App () {
  const [show, setShow] = useState(true)
  return (
    <>
      {show && <Son />}
      <button onClick={() => setShow(false)}>unmount</button>
    </>
  )
}

export default App

3.5.3 class组件通信

import { Component } from "react"
// 1. 父传子  直接通过prop子组件标签身上绑定父组件中的数据即可
// 2. 子传父  在子组件标签身上绑定父组件中的函数,子组件中调用这个函数传递参数

// 子组件
class Son extends Component {
  render () {
    // 使用this.props.msg
    return <>
      <div>我是子组件 {this.props.msg}</div>
      <button onClick={() => this.props.onGetSonMsg('我是son组件中的数据')}>sendMsgToParent</button>
    </>
  }
}

// 父组件
class Parent extends Component {
  state = {
    msg: 'this is parent msg'
  }

  getSonMsg = (sonMsg) => {
    console.log(sonMsg)
  }

  render () {
    return <div>我是父组件<Son msg={this.state.msg} onGetSonMsg={this.getSonMsg} /></div>
  }
}

function App () {
  return (
    <>
      <Parent />
    </>
  )
}

export default App

四、集中状态管理

4.1 Redux

Redux 是React最常用的集中状态管理工具,类似于Vue中的Pinia(Vuex),可以独立于框架运行,中文官网:Redux 中文官网 Redux调试工具:Redux DevTools

在React中使用redux,需安装插件 - Redux Toolkit 和 react-redux

        Redux Toolkit(RTK),是编写Redux逻辑的方式一套工具的集合集。

        react-redux - 用来 链接 Redux 和 React组件 的中间件

使用方法

① 在项目中配置 Redux Toolkit 和 react-redux

npm i @reduxjs/toolkit react-redux

② store目录结构设计

③ 定义store并注入react

  • 定义counterStore.js子模块,实现加减功能。
// counterStore.js
import { createSlice } from "@reduxjs/toolkit";

const counterStore = createSlice({
  name: "counter",
  // 初始化state
  initialState: {
    count: 0
  },
  // 修改状态的方法 同步方法 支持直接修改
  reducers: {
    inscrement(state) {
      //加
      state.count++;
    },
    decrement(state) {
      // 减
      state.count--;
    }
  }
});

// 解构并按需导出actionCreater函数
export const { inscrement, decrement } = counterStore.actions;

// 以默认导出的方式导出reducer
export default counterStore.reducer;
  • 将子模块导入至入口文件index.js
// src/store/index.js
import { configureStore } from "@reduxjs/toolkit";
// 导入子模块reducer
import counterReducer from "./modules/channelStore";
// configureStore创建一个redux数据
const store = configureStore({
  reducer: {
    counter: counterReducer
  }
});
export default store;
  • 为React注入store 
// 项目入口文件index.js
/* 
react-redux负责把Redux和React 链接 起来,
内置 Provider组件 通过 store 参数把创建好的store实例注入到应用中,
链接正式建立
 */
import store from './store'
import { Provider } from 'react-redux'

const root = ReactDOM.createRoot(document.getElementById('root'))
root.render(
  <Provider store={store}>
    <App />
  </Provider>
)

④ 在React组件使用store中的数据 

  • 获取store中的数据 - useSelector
// react组件
import { useSelector } from 'react-redux'

function App () {
// 获取store中的count
  const { count } = useSelector(state => state.counter)
  return (
    <div className="App">
      {count}
    </div>
  )
}

export default App

  • 修改store中的数据 - useDispatch
// react组件
import { useDispatch, useSelector } from 'react-redux'

// 导入子模块中修改数据的加减方法
import { inscrement, decrement } from './store/modules/counterStore'

function App () {
  const { count } = useSelector(state => state.counter)

  // 通过useDispatch派发事件
  const dispatch = useDispatch()

  return (
    <div className="App">
      {/* 调用dispatch提交action对象 */}
      <button onClick={() => dispatch(decrement())}>-</button>
      {count}
      <button onClick={() => dispatch(inscrement())}>+</button>
    </div>
  )
}

export default App
  • store中的数据传递参数

        a.在子模块定义一个reducer

import { createSlice } from "@reduxjs/toolkit"

const counterStore = createSlice({
  name: 'counter',
  // 初始化state
  initialState: {
    count: 0
  },
  // 修改状态的方法 同步方法 支持直接修改
  reducers: {
    addToNum (state, action) {
    /** 
     * action 里面有 type 和 payload 两个属性,所有的传参都在payload里面
    */
      state.count = action.payload
    }
  }
})

// 解构并按需导出actionCreater函数
export const { addToNum } = counterStore.actions;

// 以默认导出的方式导出reducer
export default counterStore.reducer;

        b.组件中使用

// react组件
import { useDispatch, useSelector } from 'react-redux'
// 导入actionCreater
import { addToNum } from './store/modules/counterStore'

function App () {
  const { count } = useSelector(state => state.counter)
  const dispatch = useDispatch()
  return (
    <div className="App">
      {count}
      {/* 调用dispatch提交action对象并传入参数 */}
      <button onClick={() => dispatch(addToNum(10))}>add To 10</button>
    </div>
  )
}

export default App

 ⑤ 持久化方案 - Redux-Persist

  • 装包

npm i redux-persist

  • 在store入口文件中配置
// src/store/index.js
import { configureStore } from "@reduxjs/toolkit";
import counterReducer from "./modules/counterStore";

// 导入两个相关包
import { persistStore, persistReducer } from "redux-persist";
import storage from "redux-persist/lib/storage"; // defaults to localStorage for web

// 定义持久化配置
const persistConfig = {
  key: "root", // 本地存储的键名
  storage, // 使用的存储引擎,默认为 localStorage
  whitelist: ["counter"] // 白名单,需要持久化的 reducer 的名字,不设置则全部缓存
};
// 将 reducer 与持久化配置整合
const persistedReducer = persistReducer(persistConfig, counterReducer);

// 创建store
const store = configureStore({
  reducer: persistedReducer,
  devTools: true, // 是否开启开发者工具,默认true
  // 配置中间件:如果使用redux-persist,则需要设置为false,否则控制台报错(非序列化数据)
  middleware: (getDefaultMiddleware) => getDefaultMiddleware({
	   serializableCheck: false,
  })
});

// 创建持久化后的store
const persistor = persistStore(store);
// 导出store和持久化后的store
export {
	store,
	persistor
}
  • 在项目入口文件中注入
// index.js 项目入口
import React from "react";
import ReactDOM from "react-dom/client";
import "./index.css";
import App from "./App";
import reportWebVitals from "./reportWebVitals";
import { Provider } from "react-redux";
// 导入redux-persist提供PersistGate组件
import { PersistGate } from "redux-persist/integration/react";
import { store, persistor } from "./store";

const root = ReactDOM.createRoot(document.getElementById("root"));
root.render(
  <Provider store={store}>
    {/* 开启持久化 */}
    <PersistGate loading={null} persistor={persistor}>
      <App />
    </PersistGate>
  </Provider>
);

reportWebVitals();

4.2 zustand

        zustand - 极简的状态管理工具,可维护多个状态,异步操作无需特殊处理,官网:Zustand Documentation,国内翻译的中文教程:[翻译] zustand 文档(readme) 

        使用方法:

// zustand
import { create } from "zustand";

// 1. 创建store
const useStore = create((set) => {
  return {
    // 状态数据
    count: 0,

    // 修改状态数据的方法(异步方法与之相同,建议使用async与await)
    inc: () => {
      // 语法1:参数是函数 需要用到老数据的场景
      set((state) => ({ count: state.count + 1 }));
      // 语法2:参数直接是一个对象
      // set({ count: 100 })
    },

    // 传递参数
    update: (newCount) => set({ count: newCount })
  };
});

// 2.组件中使用
function App() {
  const { count, inc, update } = useStore();
  return (
    <>
      <button onClick={inc}>{count}</button>
      <button onClick={() => update(10)}>传值count</button>
    </>
  );
}

export default App;

        切片模式: 当单个store比较大时,可以采用切片模式进行模块拆分组合,类似与模块化

① 创建store目录进行模块化:

② 定义 channelStore 

export const createChannelStore = (set) => {
  return {
    channelList: [],
    // 异步操作
    fetchGetList: async () => {
      const res = await fetch("http://xxx");
      const jsonRes = await res.json();
      set({
        channelList: jsonRes.data.channels
      });
    }
  };
};

② 定义 counterStore 

export const createCounterStore = (set) => {
  return {
    count: 0,
    inc: () => {
      set((state) => ({ count: state.count + 1 }));
    }
  };
};

③ 在入口文件(store/index.js)组合

import { create } from "zustand";
// 导入子模块
import { createCounterStore } from "./module/counterStore";
import { createChannelStore } from "./module/channelStore";
// 创建store
const useStore = create((...a) => {
  return {
    ...createCounterStore(...a),
    ...createChannelStore(...a)
  };
});

export default useStore;

④ 在组件中使用 

import { useEffect } from "react"
// 导入store
import  useStore from "./store"

function App () {
  // 组件使用
  const { count, inc, fetchGetList, channelList } = useStore()
  useEffect(() => {
    fetchGetList()
  }, [fetchGetList])
  return (
    <>
      <button onClick={inc}>{count}</button>
      <ul>
        {
          channelList.map(item => <li key={item.id}>{item.name}</li>)
        }
      </ul>
    </>
  )
}

export default App

五、Router

5.1 基本使用

1、搭建开发环境 react-router-dom版本为6

npm i react-router-dom

 2、创建并注入路由实例

① 在 src 目录下新建目录router,创建 index.js 文件(初始化路由)

// src/router/index.js
// 导入页面组件
import Login from '../page/Login'
import NotFound from '../page/NotFound'

// 导入创建路由的函数
import { createBrowserRouter } from 'react-router-dom';

// 创建router路由实例对象,并配置路由对应关系(路由数组)
const router = createBrowserRouter([
  {
    // 需要访问的路径
    path: '/',
    // 和路径对应的组件
    element: <Login />
  },
  {
    // 404路由配置
    path: '*',
    element: <NotFound />
  }
]);

export default router;

 ② 注入路由实例

// src/index.js
import React from 'react';
import ReactDOM from 'react-dom/client';
// 注入
import { RouterProvider } from 'react-router-dom';
import router from './router';

const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
  <React.StrictMode>

    {/* 使用 redux 的时候,需要用 Provider 包裹 RouterProvider */}
    {/* 路由绑定,注入路由实例对象 */}
    <RouterProvider router={router} />

  </React.StrictMode>
);

5.2 嵌套路由 

        初始化路由:使用 children 属性配置二级路由,配置默认二级路由:只需要在二级路由的位置将path替换为index 属性为true 即可。

// src/router/index.js
const router = createBrowserRouter([
  {
    path: '/',
    element: <Layout />,
    children: [
      {
        index: true, // 设置为默认二级路由 一级路由访问的时候,它也能得到渲染
        element: <Board />
      },
      {
        path: 'about',
        element: <About />
      }
    ]
  }
])

         二级路由出口:使用 <Outlet />组件 配置 二级路由 渲染位置

import { Outlet } from 'react-router-dom';
// Layout 组件
const Layout = () => {
  return (
    <div>
      <div>一级路由</div>
      
      {/* 二级路由出口 */}
      <Outlet />
    </div>
  );
};

export default Layout;

5.3 路由导航 

1、声明式导航

        通过 <Link /> 组件的 to 属性 指定 要跳转的 路由path,组件会被渲染为浏览器支持的a链接,如果需要 传参 直接通过 字符串拼接 的方式 拼接参数 即可。

        语法:

import { Link } from 'react-router-dom';

<Link to={'path路径'}>XXX</Link>

2、编程式导航 

        编程式导航是指通过 useNavigate   钩子得到导航方法,然后通过调用方法以命令式的形式 进行路由跳转。

        基本使用:

// 导入
import { useNavigate } from 'react-router-dom'

// 获取useNavigate
const navigate = useNavigate()

// 使用
<button onClick={() => navigate('/article')}>跳转到文章页</button>
<button onClick={() => navigate(-1)}>返回上一级</button>

        useNavigate 配置参数:

// 导入
import { useNavigate } from 'react-router-dom'

const navigate = useNavigate()

interface NavigateOptions {
  replace?: boolean; // 是否替换当前导航
  state?: any; // 包含一个可选值以存储在历史记录状态中,然后可以通过 useLocation 在目标路由上访问该值
  // preventScrollReset?: boolean;
  // relative?: RelativeRoutingType;
  // unstable_flushSync?: boolean;
  // unstable_viewTransition?: boolean;
}

navigate(to: string, options?: NavigateOptions)

// 常见用法
navigate("/new-route", { replace: true });

 3、路由导航传参

以 编程式导航传参 为例,声明式导航与之相同;

① searchParams 传参(字符串拼接)

  • 传参
import { useNavigate } from 'react-router-dom';

const navigate = useNavigate();

navigate('/article?userId=1001&articleId=abcd');
  • 接收参数
// 调用 useSearchParams 钩子函数,
// 从返回结果(数组)中解构出params对象,
// 调用该对象的get方法,
// 将需要的 参数名字 传递给get方法即可

import { useSearchParams } from 'react-router-dom';

const [params] = useSearchParams();

const userId = params.get('userId')

② params 传参

  • 改造路由:需要对路由规则数组进行改造(用 /:参数名 进行占位)
// src/router/index.js
const router = createBrowserRouter([
  {
    // path改造
    path: '/login/:userId/:likeId',
    element: <Login />
  }
]);
  • 传参:
import { useNavigate }  from 'react-router-dom';

const navgiate = useNavigate();

navigate('/article/1001/abcd');
  • 接收参数:
// 导入并调用 useParams 钩子函数,
// 该钩子函数的返回值是个对象,
// 传递的参数在这个对象中

import { useParams } from 'react-router-dom';

const params = useParams();

const userId = params.userId

const likeId = params.likeId

5.4 路由模式

创建 history 路由 - createBrowserRouter

// src/router/index.js

import { createBrowserRouter } from 'react-router-dom'

const router = createBrowserRouter([...])

export default router

 创建 hash 路由 - createHashRouter

// src/router/index.js

import { createHashRouter } from 'react-router-dom'

const router = createHashRouter([...])

export default router

两种路由模式区别

路由模式url表现底层原理是否需要后端支持
historyurl/loginhistory对象 + pushState事件
hashurl/#/login监听hashChange事件

5.5 路由懒加载

        路由懒加载是指路由的JS资源只有在被访问时才会动态获取,目的是为了 优化项目首次打开的时间

         使用方法 :

1、把路由修改为由React提供的 lazy 函数进行动态导入 

2、使用React内置的 Suspense组件 包裹路由中element选项对应的组件

// src/router/index.js
import { createBrowserRouter } from "react-router-dom";
import { Suspense, lazy } from "react";
// 1、lazy函数对组件进行导入
const Login = lazy(() => import("../page/Login"));

const router = createBrowserRouter([
  {
    path: "/",
    // 2、Suspense组件 包裹 路由组件,fallbacb(可选属性)可以设置加载中的效果
    element: (
      <Suspense fallback={"加载中..."}>
        <Login />
      </Suspense>
    )
  }
]);

export default router;

5.6 路由权限控制 

        路由权限控制常见方案是通过定义高阶组件来控制,以下场景模拟登录时的权限控制:

1、新建 src/components/AuthRoute.js 文件定义高阶组件:

// src/components/AuthRoute.j
// 核心逻辑: 有token 正常跳转  无token 去登录
import { getToken } from '@/utils' // 模拟获取token
import { Navigate } from 'react-router-dom'

export function AuthRoute ({ children }) {
  const token = getToken()
  if (token) {
    return <>{children}</>
  } else {
    return <Navigate to={'/login'} replace />
  }
}

2、在路由初始中配置:用定义的高阶组件将需要权限的路由将其包裹

// src/router/index.js

import ... // 导入路由组件
import { createBrowserRouter } from 'react-router-dom'
// 导入高阶组件
import { AuthRoute } from '@/components/AuthRoute'

const router = createBrowserRouter([
  {
    path: "/",

    // 需要进行权限验证的路由使用<AuthRoute>组件包裹
    element: <AuthRoute> <Layout /></AuthRoute>,

    children: [
      {
        index: true,
        element: <Home />
      },
      {
        path: 'article',
        element:<Article />
      },
      {
        path: 'publish',
        element: <Publish />
      }
    ]
  },
  {
    path: "/login",
    element: <Login />
  }
])

export default router

 六、React+TypeScript

6.1 useState

1、自动推导

通常React会根据传入useState的默认值来自动推导类型,不需要显式标注类型

// 自动推导value为boolean类型
const [value, toogle] = useState(false)

2、传递泛型参数

① 限制useState函数参数的初始值必须满足类型为: User | ()=> User

        定义类型User

interface User {
  name: string
  age: number
}

        User

const [user, setUser] = useState<User>({
    name: 'jack',
    age: 18,
  })

        ( )=> User

const [user, setUser] = useState<User>(() => {
    return {
      name: 'jack',
      age: 18,
    }
  })

②  限制setUser函数的参数必须满足类型为:User | ()=> User | undefined

const [user, setUser] = useState<User>({
    name: 'jack',
    age: 18,
  })

  const changeUser = () => {
    // User
    setUser({
      name: 'john',
      age: 28,
    })

    // ()=> User
    setUser(() => ({
      name: 'john',
      age: 28,
    }))
  }

3、初始值为null

当我们不知道状态的初始值是什么,将useState的初始值为null是一个常见的做法,可以通过具体类型联合null来做显式注解

function App() {
  const [user, setUser] = useState<User | null>(null)

  const changeUser = () => {
    // 限制setUser函数的参数类型可以是 User | null
    setUser(null)
    setUser({
      name: 'jack',
      age: 18,
    })
  }
  // 为了类型安全  可选链做类型守卫
  // 只有user不为null(不为空值)的时候才进行点运算
  return <>this is app {user?.age}</>
}

6.2 props

为组件prop添加类型,本质是给函数的参数做类型注解 

1、基础用法

interface Props {
  // 必选参数
  className: string
  // 可选参数
  title?: string
}

// 子组件
function Button(props: Props) {
  const { className } = props
  return <button className={className}>click me </button>
}

// 父组件
function App() {
  return (
    <>
      <Button className="test" title="this is title" />
    </>
  )
}

2、为children添加类型

children是一个比较特殊的prop, 支持多种不同类型数据的传入,需要通过一个内置的ReactNode类型来做注解,注解之后,children可以是多种类型,包括:React.ReactElement 、string、number、React.ReactFragment 、React.ReactPortal 、boolean、 null 、undefined

interface Props {
  children: React.ReactNode
}

// 子组件
function Button(props: Props) {
  const { children } = props
  return <button>{children} </button>
}

// 父组件
function App() {
  return (
    <>
      <Button>
        <span>this is span</span>
      </Button>
    </>
  )
}

3、为事件prop添加类型

场景:子传父

interface Props {
  onGetMsg?: (msg: string) => void
}

function Son(props: Props) {
  const { onGetMsg } = props
  const clickHandler = () => {
    onGetMsg?.('this is msg')
  }
  return <button onClick={clickHandler}>sendMsg</button>
}

function App() {
  const getMsgHandler = (msg: string) => {
    console.log(msg)
  }
  return (
    <>
      <Son onGetMsg={getMsgHandler} />
    </>
  )
}

七、免责声明

  1. 本博客中的文章摘自网上的众多博客,仅作为自己知识的补充和整理,并分享给其他需要的 coder,不会用于商用;
  2. 因为很多博客的地址已经记不清楚了,所以不会在这里标明出处;
  3.  React中文官网:React 官方中文文档

  4.  Redux中文官网:Redux 中文官网

  5.  React Redux中文官网:React Redux 中文文档 | React Redux 中文文档

  6.  React-Router官网:Home v6.22.3 | React Router

  7.  TypeScript学习网站:TypeScript 教程 - 网道

  8.  Ant-Design官网:Ant Design - 一套企业级 UI 设计语言和 React 组件库

  9. Ant-Design Mobile 官网:Ant Design Mobile - Ant Design Mobile

  10.  webpack官网:概念 | webpack 中文文档 | webpack中文文档 | webpack中文网

  11.  vite官网:开始 | Vite 官方中文文档

  12.  React 生态清单:【前端】一个”哇塞“前端需要知道的 React 生态清单 ~ - 掘金

  • 26
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值