React学习笔记,从入门到砸门

news2024/11/26 2:38:11

项目构建命令

npx create-react-app react-basic
npx:node语法
create-react-app:项目模板
react-basic:项目名称

项目结构

image.png

项目打包和本地预览

  1. 项目打包npm run build
  2. 本地预览(模拟服务器运行项目)
    1. 安装本地服务包 npm i -g serve
    2. 指定服务启动路径,如./build:serve -s ./build

打包优化 - CDN配置

  1. craco.config.js
// 扩展webpack的配置

const path = require("path");
// 引入辅助函数
const { whenProd, getPlugin, pluginByName } = require("@craco/craco");

module.exports = {
  // webpack 配置
  webpack: {
    // 配置别名
    alias: {
      // 约定:使用 @ 表示 src 文件所在路径
      "@": path.resolve(__dirname, "src"),
    },
    // 配置CDN
    configure: (webpackConfig) => {
      let cdn;
      whenProd(() => {
        // key: 不参与打包的包(由dependencies依赖项中的key决定)
        // value: cdn文件中 挂载于全局的变量名称 为了替换之前在开发环境下
        webpackConfig.externals = {
          react: "React",
          "react-dom": "ReactDOM",
        };
        // 配置现成的cdn资源地址
        // 实际开发的时候 用公司自己花钱买的cdn服务器
        cdn = {
          js: [
            "https://cdnjs.cloudflare.com/ajax/libs/react/18.1.0/umd/react.production.min.js",
            "https://cdnjs.cloudflare.com/ajax/libs/react-dom/18.1.0/umd/react-dom.production.min.js",
          ],
        };
      });
      // 通过 htmlWebpackPlugin插件 在public/index.html注入cdn资源url
      const { isFound, match } = getPlugin(
        webpackConfig,
        pluginByName("HtmlWebpackPlugin")
      );
      if (isFound) {
        // 找到了HtmlWebpackPlugin的插件
        match.options.cdn = cdn;
      }
      return webpackConfig;
    },
  },
};
  1. index.html:动态创建script标签,导入js文件
<body>
  <div id="root"></div>
  <% htmlWebpackPlugin.options.cdn.js.forEach(cdnURL=> { %>
      <script src="<%= cdnURL %>"></script>
      <% }) %>
</body>

包体积可视化分析

  1. 安装插件:npm i source-map-explorer
  2. 配置命令指定要分析的js文件:一般为打包后的js文件

package.json:

"scripts": {
  "analyze": "source-map-explorer 'build/static/js/*.js'"
},

配置路径别名

CRA本身把webpack配置包装到了黑盒里无法直接修改,需要借助一个插件 - craco
配置步骤

  1. 安装craco:npm i -D @craco/craco
  2. 项目根目录下创建配置文件craco.config.js
  3. 配置文件中添加路径解析配置
  4. 包文件中配置启动和打包命令
const path = require('path')

module.exports = {
    // webpack配置
  webpack: {
    // 配置别名
    alias: {
    // 约定:使用 @ 表示 src 文件所在路径
      '@': path.resolve(__dirname, 'src'),
    },
  },
}
"scripts": {
  "start": "craco start",
  "build": "craco build"
},

联系路径配置

VsCode的联想配置,需要我们在项目目录下添加jsconfig.json文件,加入配置之后Vscode会自定读取配置帮助我们自动联想提示
配置步骤

  1. 根目录下新增配置文件 - jsconfig.json
  2. 添加路径提示配置
{
  "compilerOptions": {
    "baseUrl": "./",
      "paths": {
      "@/*": [
        "src/*"
      ]
    }
  }
}

React调试插件

浏览器插件:React Developer Tools

JSX基础

概念和本质

概念:jsx是javaScript和XML(HTML)的缩写,表示在js代码中编写html模板结构,它是react中编写UI模板的方式
image.png
本质:JSX并不是标准的js语法,它是js的语法扩展,浏览器本身不能识别,需要通过解析工具做解析之后才能在浏览器中运行
image.png
优势

  1. HTML的声明式模板语法
  2. JS的可编程能力

JSX中使用JS表达式

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

  1. 使用引号传递字符串
  2. 使用JavaScript变量
  3. 函数调用和方法调用
  4. 使用JavaScript对象

注意:if语句、switch语句、变量声明属于语句,不是表达式,不能出现在{}中

const count = 100

const getName = () => {
  return 'Bob'
}
function App() {
  return (
    <div className="App">
      {/* 使用引号传递字符串 */}
      { '这是一个字符串' }
      {/* 使用JavaScript变量 */}
      { count }
      {/* 函数调用 */}
      { getName() }
      {/* 方法调用 */}
      { new Date().getDate() }
      {/* 使用JavaScript对象 */}
      <div style={ {color:'red',fontSize:'30px'} }>this is div</div>
    </div>
  );
}

export default App;

JSX中实现列表渲染

语法:在JSX中可以使用原生JS中的map方法遍历渲染列表

  1. 循环哪个结构在map中就return哪个结构
  2. 结构上需要加上key字段,可以是字符串、number类型
  3. key的作用:React框架内部使用,提升更新性能
const list = [
  {id:1,name:'Vue'},
  {id:2,name:'React'},
  {id:3,name:'Angle'}
]
function App() {
  return (
    <div className="App">
      {/* 渲染列表 */}
      <ul>
        {list.map(item => <li key={item.id}>{item.name}</li>)}
      </ul>
    </div>
  );
}

export default App;

JSX实现条件渲染

语法:在React中,可以通过逻辑与运算符&&、三元里表达式(?:)实现基础的条件渲染
{flag && <span>this is span</span>}:当flag为false时,不显示;当flag为true时,显示标签
{loading ? <span>loading...</span> : <span>this is span</span>}:当loading为true时显示第一个标签;当loading为false时显示第二个标签

const isLogin = true
function App() {
  return (
    <div className="App">
     {/* 条件渲染 */}
     {/* 逻辑与 && */}
     {isLogin && <div>未登录</div>}
     {/* 三元运算符 */}
     {isLogin ? <div>已登录(jack)</div> : <div>用户未登录</div>}
    </div>
  );
}

export default App;

image.png
复杂的条件渲染:

const count = 1 // 0  1  2

const getCount = () => {
  if(count === 0){
    return '当前为0'
  }else if(count === 1){
    return '当前为1'
  }else if(count === 2){
    return '当前为2'
  }
}
function App() {
  return (
    <div className="App">
     {/* 复杂的条件渲染 */}
     {getCount()}
    </div>
  );
}

export default App;

事件绑定

基础事件绑定

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

function App() {
  const handleClick = () => {
    console.log('BUTTON被点击了')
  }
  return (
    <div className="App">
      {/* 基础事件绑定 */}
      <button onClick={handleClick}>click事件</button>
    </div>
  );
}

export default App;

使用事件对象参数

语法:在事件回调函数中设置形参e

function App() {
  const handleClick1 = (e) => {
    console.log('button被点击了',e)
  }
  return (
    <div className="App">
      {/* 使用事件对象参数e */}
      <button onClick={handleClick1}>click事件</button>
    </div>
  );
}

export default App;

传递自定义参数

语法:事件绑定的位置改造成箭头函数的写法,在执行实际处理业务函数的时候传递实参

function App() {
  const handleClick2 = (name) => {
    console.log('button被点击了',name)
  }
  return (
    <div className="App">
      {/* 传递自定义参数 */}
      <button onClick={() => handleClick2('bob')}>click事件</button>
    </div>
  );
}

export default App;

同时传递事件对象和自定义参数

语法:在事件绑定的位置传递事件实参e和自定义参数,clickHandler中声明形参,注意顺序对应

function App() {
  const handleClick3 = (name,e) => {
    console.log('button被点击了',name,e)
  }
  return (
    <div className="App">
      {/* 同时传递自定义参数和事件对象 */}
      <button onClick={(e) => handleClick3('bob',e)}>click事件</button>
    </div>
  );
}

export default App;

组件基础使用

概念:一个组件就是用户界面的一部分,它可以有自己的逻辑和外观,组件之间可以互相嵌套,也可以复用多次
语法:在React中,一个组件就是首字母大写的函数,内部存放了组件的逻辑和视图UI,渲染组件只需要把组件当成标签书写即可

// 定义组件
function Button(){
  // 组件业务逻辑
  return <button>click me</button>
}

function App() {
  
  return (
    <div className="App">
      {/* 使用组件方式: */}
      {/* 1.自闭和 */}
      <Button/>
      {/* 2.成对标签 */}
      <Button></Button>
    </div>
  );
}

export default App;

useState基础使用

:::info
useState是一个React Hook(函数),它允许我们向组件添加一个状态变量,从而控制影响组建的渲染结果
本质:和普通的JS变量不同的是,状态变量一旦发生变化组件的视图UI也会跟着变化(数据驱动视图)
:::

  1. useState是一个函数,返回值是一个数组
  2. 数组中的第一个参数是状态变量,第二个参数时set函数用来修改状态变量
  3. useState的参数将作为count的初始值

set函数的作用:

  1. 使用新值更新状态变量
  2. 使用更新后的状态变量重新渲染UI
import { useState } from "react";

function App() {
  // 1. 调用useState函数创建一个状态变量
  // count:状态变量
  // setCount:修改状态变量的方法
  const [count,setCount] = useState(0)
  
  // 2. 点击事件回调
  const handleClick = () => {
    // 作用:
    // 1. 用传入的新值修改count
    // 2. 使用新的count重新渲染UI
    setCount(count+1)
  }
  return (
    <div className="App">
      <button onClick={handleClick}>{count}</button>
    </div>
  );
}

export default App;

修改状态的规则

  1. 状态不可变:在React中,状态被认为是只读的,我们应该始终替换它而不是修改它,直接修改状态不能引发视图更新
  2. 修改对象状态:对于对象类型的状态变量,应该始终传给set方法一个全新的对象来进行修改
import { useState } from "react";

function App() {
  let [count,setCount] = useState(0)

  const handleClick = () => {
    // 错误写法:直接修改
    // 1. 值发生了变化
    // 2. 但无法引发视图更新
    // count++
    // console.log(count) // 自增

    // 正确写法:set函数
    // 1. 值发生了变化
    // 2. 视图重新渲染
    setCount(count + 1)
  }

  // 修改对象状态
  const [form,setForm] = useState({name:'bob'})

  const changeForm = () => {
    // 错误写法:直接修改
    // 1. 值发生了变化
    // 2. 但无法引发视图更新
    // form.name = 'tom'
    // console.log(form) // tom

    // 正确写法:set函数
    // 1. 值发生了变化
    // 2. 视图重新渲染
    setForm({
      ...form,
      name:'Tom'
    })
  }
  return (
    <div className="App">
      <button onClick={handleClick}>{count}</button>
      <button onClick={changeForm}>修改form-{form.name}</button>
    </div>
  );
}

export default App;

基础样式控制

React组件基础的样式控制有两种方式:

  1. 行内样式 — 不推荐
  2. class类名控制,注意不能使用class,而是使用className — 推荐
.foo{
    color: blue;
}
// 导入样式文件
import './index.css'

const myStyle = {
  color:'yellow',
  fontSize: '50px'
}
function App() {
  return (
    <div className="App">
      {/* 行内样式 */}
      <span style={{color:'red',fontSize:'40px'}}>this is span</span>
      <span style={myStyle}>this is span</span>
      {/* class类名控制 */}
      <span className='foo'>this is class</span>
    </div>
  );
}

export default App;

classnames优化类名控制

classnames是一个简单的JS库,可以非常方便的通过条件动态控制class类名显示
安装npm install classnames
不使用classnames时,动态控制class类名:采用字符串拼接的方式

<span className={`tab ${item.type === type && 'active'}`}>bob</span>

使用classnames时,动态控制class类名:

<span className={classNames('tab',{active: item.type === type})}>bob</span>

image.png

表单受控绑定

概念:使用React组件的状态(useState)控制表单的状态
image.png

import { useState } from 'react';

function App() {
  const [value,setValue] = useState('')
  return (
    <div className="App">
      {/* 通过value属性绑定状态,通过onChange属性绑定状态同步的函数 */}
      <input value={value} type='text' onChange={(e) => setValue(e.target.value)}/>
      <div>{value}</div>
    </div>
  );
}

export default App;

获取DOM

APIuseRef(null)
使用方法

  1. 使用useRef生成ref对象,绑定到dom标签上

const inputRef = useRef(null)
<input type='text' ref={inputRef}/>

  1. dom可用时,ref.current获取dom,注意:dom在渲染完毕(dom生成)之后才可用

inputRef.current

// 导入样式文件
import { useRef } from 'react';

function App() {
  // 1. 使用useRef生成ref对象,绑定到dom标签上
  const inputRef = useRef(null)
  const showDom = () => {
    // 2. dom可用时,ref.current获取dom
    // dom在渲染完毕(dom生成)之后才可用
    console.dir(inputRef.current)
  }
  return (
    <div className="App">
      <input type='text' ref={inputRef}/>
      <button onClick={showDom}>获取Dom</button>
    </div>
  );
}

export default App;

组件通信

props说明

  1. props可传递数据类型:

数字、字符串、布尔值、数组、对象、函数、JSX

  1. props是只读对象

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

function Son(props){
  console.log(props)
  // props.age = 32  错误代码,TypeError: Cannot assign to read only property 'age' of object '#<Object>'
  return (
    <div>this is Son App,{props.name}</div>
  )
}

function App() {
  const name = 'this is App'
  return (
    <div className="App">
      <Son 
        name={name} 
        age={18} 
        isTrue={true} 
        list={['vue','react']} 
        obj={{name:'bob'}} 
        cb={() => console.log(123)} 
        child={<span>this is span child</span>}
      />
    </div>
  );
}

export default App;

image.png

特殊的prop - children

场景:当我们把内容嵌套在子组件标签中时,父组件会自动在名为children的prop属性中接收该内容
语法

// 子组件中:
function Son(props){
  return (
    <div>{props.children}</div>
  )
}

// 父组件中:
<Son>
  <span>this is span</span>
</Son>

比如

function Son(props){
  // 2. 子组件通过props参数接收数据
  console.log(props)
  return (
    <div>this is Son App,{props.name}{props.children}</div>
  )
}

function App() {
  const name = 'this is App'
  return (
    <div className="App">
      {/* 1. 在子组件标签上绑定属性 */}
      <Son name={name}>
        <span>this is span</span>
      </Son>
    </div>
  );
}

export default App;

image.png

父传子

实现步骤

  1. 父组件传递数据 - 在子组件标签上绑定属性
  2. 子组件接收数据 - 子组件通过props参数接收数据
function Son(props){
  // 2. 子组件通过props参数接收数据
  return (
    <div>this is Son App,{props.name}</div>
  )
}

function App() {
  const name = 'this is App'
  return (
    <div className="App">
      {/* 1. 在子组件标签上绑定属性 */}
      <Son name={name}/>
    </div>
  );
}

export default App;

子传父

实现核心:在子组件中调用父组件中的函数并传递参数
语法

// 子组件:
function Son(props){
  const {onGetSonMsg,onGetSonName} = props
  return <button onClick={() => onGetSonMsg(sonMsg) }>点击</button>
}

// 父组件:
const getSonMsg = (msg) => {
    // 接收子组件传递的参数
    console.log(msg) // this is son msg
}
<Son name={name} onGetSonMsg={getSonMsg}/>

比如

import { useState } from "react"

function Son(props){
  // 2. 子组件通过props参数接收数据
  const {onGetSonMsg,onGetSonName} = props
  console.log(props)

  const name = 'this is Son'
  const sonMsg = 'this is son msg'
  return (
    <div>
      this is Son
      <div>{props.name}</div>
      {/* 3. 触发父组件传递过来的函数,并传递参数 */}
      <button onClick={() => onGetSonMsg(sonMsg) }>点击</button>
      <button onClick={() => onGetSonName(name)}>点击2</button>
    </div>
  )
}

function App() {
  const [sonName,setSonName] = useState('')

  const name = 'this is App'
  const getSonMsg = (msg) => {
    // 4. 接收子组件传递的参数
    console.log(msg) // this is son msg
  }

  const getSonName = (sname) => {
    // 4. 接收子组件传递的参数
    setSonName(sname)
  }
  return (
    <div className="App">
      {/* 1. 在子组件标签上绑定属性 */}
      <Son name={name} onGetSonMsg={getSonMsg} onGetSonName={getSonName}/>
      <div>{sonName}</div>
    </div>
  );
}

export default App;

使用状态提升实现兄弟组件通信

实现思路:A(子组件) —> App(父组件) —> B(子组件)
实现步骤

  1. A组件先通过子传父的方式把数据传给父组件App
  2. App拿到数据后通过父传子的方式再传递给B组件
import { useState } from "react"

function A({onGetName}) {
  const aName = 'this is A'
  return (
    <div>
      this is A
      {/* 1.将数据传递父组件App */}
      <button onClick={() => onGetName(aName)}>send</button>
    </div>
  )
}

function B(props) {
  const {name} = props
  return (
    <div>
      this is B
      <div>{name}</div>
    </div>
  )
}

function App() {
  const [name,setName] = useState('')
  const getName = (aName) => {
    setName(aName)
  }
  return (
    <div className="App">
      <A onGetName={getName}/>
      {/* 2.将数据传递给子组件B */}
      <B name={name}/>
    </div>
  );
}

export default App;

使用Context机制跨层级组件通信

语法createContextuseContext
实现步骤

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

// 1. 使用createContext创建一个上下文对象
const MsgContext = createContext()

function B() {
  // 3. 在底层组件中通过useContext钩子函数使用数据
  const msg = useContext(MsgContext)
  return (
    <div>
      this is B
      <div>{msg}</div>
    </div>
  )
}

function A() {
  return (
    <div>
      this is A
      <B/>
    </div>
  )
}

function App() {
  const msg = 'this is App msg'
  return (
    <div className="App">
      {/* 2. 在顶层组件中通过Provider组件提供数据 */}
      <MsgContext.Provider value={msg}>
        this is App
        <A/>
      </MsgContext.Provider>
    </div>
  );
}

export default App;

传递状态(useState):

import { createContext,useContext,useState } from "react"

// 1. 使用createContext创建一个上下文对象
const NameContext = createContext()

function B() {
  // 3. 在底层组件中通过useContext钩子函数使用数据
  const name = useContext(NameContext)
  return (
    <div>
      this is B
      <div>{name}</div>
    </div>
  )
}

function A() {
  return (
    <div>
      this is A
      <B/>
    </div>
  )
}

function App() {
  const [name,setName] = useState('bob')

  return (
    <div className="App">
      {/* 2. 在顶层组件中通过Provider组件提供数据 */}
      <NameContext.Provider value={name}>
        this is App
        <button onClick={() => setName('tom')}>改变name</button>
        <A/>
      </NameContext.Provider>
    </div>
  );
}

export default App;

useEffect

概念理解和基础使用

概念:useEffect是一个React Hook函数,用于在React组件中创建不是由事件引起而是由渲染本身引起的操作,比如发送Ajax请求,更改Dom等等
语法useEffect(() => {}, [])
参数1是一个函数,可以把它叫做副作用函数,在函数内部可以放置要执行的操作
参数2是一个数组(可选参数),在数组里放置依赖项,不同依赖项会影响第一个参数函数的执行,当是一个空数组的时候,副作用函数只会在组件渲染完毕之后执行一次

import { useEffect, useState } from "react";

const URL = 'http://geek.itheima.net/v1_0/channels'

function App() {
  // 创建一个状态
  const [list,setList] = useState([])
  useEffect(() => {
    // 获取列表数据
    async function getList(){
      const res = await fetch(URL)
      const jsonRes = await res.json()
      console.log(jsonRes)
      setList(jsonRes.data.channels)
    }

    getList()
  },[])

  return (
    <div className="App">
      this is App
      <ul>
        {list.map(item => <li key={item.id}>{item.name}</li>)}
      </ul>
    </div>
  );
}

export default App;

依赖项参数说明

useEffect副作用函数的执行时机存在多种情况,根据传入依赖项的不同,会有不同的执行表现

依赖项副作用函数执行时机
没有依赖项组件初始渲染 + 组件更新时执行(如状态变化等)
空数组依赖只在初始渲染时执行一次
添加特定依赖项组件初始渲染 + 特性依赖项变化时执行
import { useEffect, useState } from "react";

function App() {
  // 1. 没有依赖项:初始 + 组件更新
  const [count, setCount] = useState(0)
  // 改变count会触发,组件发生变化都会触发
  useEffect(() => {
    console.log('没有依赖项的副作用函数')
  })

  // 2. 传入空数组依赖:初始执行一次
  // 只在初始执行一次
  useEffect(() => {
    console.log('空数组的副作用函数')
  },[])

  // 3. 传入特定依赖项:初始 + 依赖项变化时执行
  const [name,setName] = useState('Tom')
  // 改变name会触发,同时也会触发第一个useEffect
  useEffect(() => {
    console.log('传入特定依赖项的副作用函数')
  },[name])

  return (
    <div className="App">
      this is App
      <button onClick={() => setCount(count + 1)}>+{count}</button>
      <button onClick={() => setName(name + '+')}>update:{name}</button>
    </div>
  );
}

export default App;

清除副作用

在useEffect中编写的由渲染本身引起的对接组件外部的操作,社区也经常把它叫做副作用操作,比如在useEffect中开启了一个定时器,我们想在组件卸载时把这个定时器再清理掉,这个过程就是清理副作用
说明:清除副作用的函数最常见的执行时机是在组件卸载时自动执行

import { useEffect, useState } from "react";

function Son(){
  // 在初始执行一次
  // 实现副作用操作逻辑
  useEffect(() => {
    const timer = setInterval(() => {
      console.log('定时器执行中...')
    },1000)

    return () => {
      // 在组件卸载时执行
      // 清除副作用逻辑
      clearInterval(timer)
    }
  },[])

  return (
    <div>
      this is Son
    </div>
  )
}

function App() {
  const [show,setShow] = useState(true)
  return (
    <div>
      this is App
      {show && <Son/>}
      <button onClick={() => setShow(false)}>销毁Son组件</button>
    </div>
  );
}

export default App;

自定义Hook实现

概念:zidingyiHook是以use打头的函数,通过自定义Hook函数可以用来实现逻辑的封装和复用
实现步骤

  1. 声明一个以use打头的函数
  2. 在函数体内封装可复用的逻辑
  3. 把组件中要用的状态或函数return出去,以对象或者数组的形式
import {useState } from "react";

// 自定义的Hook
function useToggle(){
  const [show,setShow] = useState(true)
  function toggle (){
    setShow(!show)
  }
  return {
    show,
    toggle
  }
}

function App() {
  const {show,toggle} = useToggle()
  return (
    <div>
      this is App
      {show && <h2>显示与隐藏</h2>}
      <button onClick={toggle}>切换</button>
    </div>
  );
}

export default App;

ReactHooks使用规则

使用规则

  1. 只能在组件中或者其他自定义Hook函数中调用
  2. 只能在组件的顶层调用,不能嵌套在if、for、其他函数中
import {useState } from "react";

// 错误的用法1:
// useState()  报错

function App() {
  if(Math.random > 5){
    // 错误的用法2:
    // useState()  报错
  }
  return (
    <div>
      this is App
    </div>
  );
}

export default App;

Redux

什么是Redux?

Redux是React最常用的集中状态管理工具,类似于Vue中的Pinia(Vuex),可以独立于框架运行
作用:通过集中管理的方式管理应用的状态

Redux管理数据流程梳理

Redux把整个数据修改的流程分成了三个核心概念,分别是:state、action、reducer

  • state - 一个对象 存放着我们管理的数据状态
  • action - 一个对象 用来描述你想怎么修改数据
  • reducer - 一个函数 根据action的描述生成一个新的state

image.png

配置工具

在React中使用redux,官方要求安装两个其他插件 - Redux Toolkit 和 react-redux

  • Redux ToolKit (RTK) - 官方推荐编写Redux逻辑的方式,是一套工具的集合集,简化书写方式
    • 简化store的配置方式
    • 内置immer支持可变式状态修改
    • 内置thunk更好的异步创建
  • react-redux - 用来链接Redux和React组件的中间件

image.png

配置基础环境

  1. 使用CRA快速创建React项目

npx create-react-app react-redux

  1. 安装配置工具

npm i @reduxjs/toolkit react-redux

  1. 启动项目

npm run start

store目录结构设计

  1. 通常集中状态管理的部分都会单独创建一个单独的‘store’目录
  2. 应用通常会有很多个子store模块,所以创建一个‘modules’目录,在内部编写业务分类的子store
  3. store中的入口文件index.js的作用是组合modules中所有的子模块,并导出store

image.png

Redux使用步骤

使用React Toolkit创建counterStore

  • initialState:仓库,store存放位置
  • reducres:修改状态唯一的方法,同步方法
import { createSlice } from "@reduxjs/toolkit"

const countStore = createSlice({
    name:'couter',
    // 初始化state
    initialState:{
        count:0
    },
    // 修改状态的方法 同步方法 支持直接修改
    reducers:{
        inscrement(state){
            state.count++
        },
        decrement(state){
            state.count--
        },
        addToNum(state,action){
            // 传递的参数都在action的payload属性中
            state.count = action.payload
        }
    }
})

// 解构出actionCreater函数
const {inscrement,decrement,addToNum} = countStore.actions
// 获取reducer
const reducer = countStore.reducer

// 以按需导出的方式导出actionCreater
export {inscrement,decrement,addToNum}
// 已默认导出的方式导出reducer
export default reducer

为React注入store

通过react-redux内置Provider组件将store实例注入应用中
主要代码:

<Provider store={store}>
  <App />
</Provider>

完整代码:

import React from 'react';
import ReactDOM from 'react-dom/client';
import App from './App';
import reportWebVitals from './reportWebVitals';

// 导入store
import store from './store';
import { Provider } from 'react-redux';

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

// If you want to start measuring performance in your app, pass a function
// to log results (for example: reportWebVitals(console.log))
// or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals
reportWebVitals();

React组件使用store中的数据

通过钩子函数useSelector把store中的数据映射到组件中
主要代码:

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

完整代码:

import { useSelector } from "react-redux";

function App() {
  // 使用store中的数据
  const {count} = useSelector(state => state.counter)
  return (
    <div className="App">
      <span>{count}</span>
    </div>
  );
}

export default App;

React组件修改store中的数据

通过hook函数useDispatch生成提交action对象的dispatch函数
主要代码:

// 1.定义reducers:
reducers:{
    decrement(state){
        state.count--
    }
}

// 2:
const dispatch = useDispatch()

// 3:调用reducers
<button onClick={() => dispatch(decrement())}>-</button>

完整代码:

import { useDispatch, useSelector } from "react-redux";
// 导入创建action对象的方法
import { decrement, inscrement } from "./store/modules/counterStore";

function App() {
  // 使用store中的数据
  const {count} = useSelector(state => state.counter)

  // 使用dispatch函数修改store
  const dispatch = useDispatch()
  return (
    <div className="App">
      {/* 掉用dispatch提交action对象:修改store */}
      <button onClick={() => dispatch(decrement())}>-</button>
      <span>{count}</span>
      <button onClick={() => dispatch(inscrement())}>+</button>
    </div>
  );
}

export default App;

提交action传参

在reducers的同步修改方法中添加action对象参数,在调用actionCreater的时候传递参数,参数会被传递到action对象payload属性上
主要语法:

// 1.定义reducers:
reducers:{
    addToNum(state,action){
        // 传递的参数都在action的payload属性中
        state.count = action.payload
    }
}

// 2.使用reducers:
const dispatch = useDispatch()

// 3.传递参数: 
<button onClick={() => dispatch(addToNum(10))}>add to 10</button>

完整代码:

import { useDispatch, useSelector } from "react-redux";

// 导入创建action对象的方法
import { addToNum } from "./store/modules/counterStore";

function App() {
  // 使用store中的数据
  const {count} = useSelector(state => state.counter) 

  // 使用dispatch函数修改store
  const dispatch = useDispatch()
  return (
    <div className="App">
      <span>{count}</span>
      {/* 掉用dispatch提交action对象:修改store */}
      {/* 传递参数 */}
      <button onClick={() => dispatch(addToNum(10))}>add to 10</button>
      <button onClick={() => dispatch(addToNum(20))}>add to 20</button>
    </div>
  );
}

export default App;

异步操作Store

  1. 创建store的写法保持不变,配置好同步修改状态的方法
  2. 单独封装一个函数,在函数内部return一个函数,在新函数中
    1. 封装异步请求获取数据
    2. 调用同步actionCreater传入异步数据生成一个action对象,并使用dispatch提交
  3. 组件中dispatch的写法保持不变

主要代码:

// 1.定义reducers:
reducers:{
    setChannels(state,action){
        state.channelList = action.payload
    }
}
// 2.在store文件中封装异步请求(channelStore.js):
const fetchChannelList = () => {
    // dispatch是一个固定参数
    return async dispatch => {
        const res = await axios.get('http://geek.itheima.net/v1_0/channels')
        dispatch(setChannels(res.data.data.channels))
    }
}

// 修改状态:
// 3.使用dispatch函数修改store
const dispatch = useDispatch()

// 4.使用useEffect触发异步请求执行:
useEffect(() => {
  dispatch(fetchChannelList())
}, [dispatch])

完整代码:

import { createSlice } from "@reduxjs/toolkit";
import axios from "axios";

const channelStore = createSlice({
    name:'channel',
    initialState:{
        channelList:[]
    },
    reducers:{
        setChannels(state,action){
            state.channelList = action.payload
        }
    }
})

// 异步请求 - 主要代码
const {setChannels} = channelStore.actions

const fetchChannelList = () => {
    // dispatch是一个固定参数
    return async dispatch => {
        const res = await axios.get('http://geek.itheima.net/v1_0/channels')
        dispatch(setChannels(res.data.data.channels))
    }
}
// 同步代码导出setChannels,异步代码导出fetchChannelList
export {fetchChannelList}

const reducer = channelStore.reducer

export default reducer
import { useDispatch, useSelector } from "react-redux";
import { useEffect } from "react";

// 导入创建action对象的方法
import { fetchChannelList } from "./store/modules/channelStore";

function App() {
  // 使用store中的数据
  const {channelList} = useSelector(state => state.channel) 

  // 使用dispatch函数修改store
  const dispatch = useDispatch()

  // 使用useEffect触发异步请求执行
  useEffect(() => {
    dispatch(fetchChannelList())
  }, [dispatch])
  return (
    <div className="App">
      {/* 渲染异步获取的数据 */}
      <ul>
        {channelList.map(item => <li key={item.id}>{item.name}</li>)}
      </ul>
    </div>
  );
}

export default App;

Redux调试工具

浏览器插件:Redux DevTools

ReactRouter

什么是前端路由

一个路径path对应一个组件component当我们在浏览器中访问一个path的时候,path对应的组件会在页面中进行渲染

安装react-router-dom

npm i react-router-dom

简单使用

image.png

  1. 创建路由
import Login from "../page/Login"
import Article from "../page/Article"

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

// 创建路由
const router = createBrowserRouter([
    {
        path: '/login',
        element: <Login />
    },
    {
        path: '/article',
        element: <Article />
    }
])

export default router
  1. 绑定路由
import React from 'react';
import ReactDOM from 'react-dom/client';
import './index.css';
import App from './App';
import reportWebVitals from './reportWebVitals';
import {RouterProvider} from 'react-router-dom'

// 1. 导入路由
import router from './router';

const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
  // 2. 绑定路由
  <RouterProvider router={router}></RouterProvider>
);

// If you want to start measuring performance in your app, pass a function
// to log results (for example: reportWebVitals(console.log))
// or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals
reportWebVitals();

路由懒加载

  1. 使用React内置的lazy函数进行导入const Home = lazy(() => import("@/pages/Home"))
  2. 使用React内置的Suspense组件包裹路由中的element选项对应的组件<Suspense><Home /></Suspense>
import { createBrowserRouter } from "react-router-dom";
import Layout from "@/pages/Layout";
import AuthRoute from "@/components/AuthRoute";
import { lazy, Suspense } from "react";


// 路由懒加载
// 1. lazy函数对组件进行导入
const Home = lazy(() => import("@/pages/Home"));

const router = createBrowserRouter([
  {
    path: "/",
    element: (
      <AuthRoute><Layout />
      </AuthRoute>
    ),
    children: [
      {
        index: true,
        element: (
          <Suspense><Home /></Suspense> // 2. 使用suspense组件包裹
        ),
      },
    ],
  },
]);

export default router;

路由导航跳转

什么是路由导航

路由系统中的多个路由之间需要进行路由跳转,并且在跳转的同时有可能需要传递参数进行通信

声明式导航

声明式导航是指通过在模块中通过<Link/>组件描述出要跳转到哪里去,<Link/>组件会被渲染为浏览器支持的a链接
语法<Link to="/article">文章</Link>

  • to属性:指定跳转的路由path
  • 传参:字符串拼接参数,如/login?id=1001&name=bob
import {Link} from 'react-router-dom'
function Login(){
  return (
    <div>
      这是登录页
      {/* 声明式导航 */}
      <Link to="/article">去文章页</Link>
    </div>
  )
}

export default Login

编程式导航

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

  1. const navigate = useNavigate()
  2. navigate('/article')
import {useNavigate} from 'react-router-dom'
function Login(){
  const navigate = useNavigate()
  return (
    <div>
      这是登录页
      {/* 编程式导航 */}
      <button onClick={() => navigate('/article')}>去文章页</button>
    </div>
  )
}

export default Login

路由导航传参

searchParams传参

主要代码:

// 传递:
navigate('/article?id=1001&name=jack')

// 接收:
const [params] = useSearchParams()
let id = params.get('id')
let name = params.get('name')

例如:

import {useNavigate} from 'react-router-dom'
function Login(){
  const navigate = useNavigate()
  return (
    <div>
      这是登录页
      {/* searchParams传参 */}
      <button onClick={() => navigate('/article?id=1001&name=jack')}>去文章页</button>
    </div>
  )
}

export default Login
import { useSearchParams } from "react-router-dom"
function Article(){
  // 接收searchParams传参
  const [params] = useSearchParams()
  let id = params.get('id')
  let name = params.get('name')
  return (
    <div>
      这是文章页{id}-{name}
    </div>
  )
}

export default Article

params传参

注意:params传参需要在路由定义时,添加占位参数
image.png
主要代码:

// 路由中定义参数:
const router = createBrowserRouter([
  {
    path: '/article/:id',
    element: <Article />
  }
])

// 传递:
navigate('/article/1001')

// 接收:
const params = useParams()
let id = params.id

例如:

import Login from "../page/Login"
import Article from "../page/Article"

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

const router = createBrowserRouter([
    {
        path: '/login',
        element: <Login />
    },
    {
        path: '/article/:id',
        element: <Article />
    }
])

export default router
import {useNavigate} from 'react-router-dom'
function Login(){
    const navigate = useNavigate()
    return (
        <div>
            这是登录页
            {/* params传参 */}
            <button onClick={() => navigate('/article/1001')}>去文章页</button>
        </div>
    )
}

export default Login
import { useParams } from "react-router-dom"
function Article(){
    // 接收params传参
    const params = useParams()
    let id = params.id
    return (
        <div>
            这是文章页{id}
        </div>
    )
}

export default Article

嵌套路由配置

什么是嵌套路由
在一级路由中又内嵌了其他路由,这种关系就叫做嵌套路由,嵌套至一级路由内的路由又称作二级路由
实现步骤:

  1. 使用children属性配置路由嵌套关系
  2. 使用<Outlet>组件配置二级路由渲染位置

image.png

const router = createBrowserRouter([
  {
    path:'/',
    element: <Layout/>,
    children:[
      {
        path:'/board',
        element: <Board/>
      },
      {
        path: '/about',
        element: <About/>
      }
    ]
  }
])
import {Outlet,Link} from 'react-router-dom'

function Layout(){
    return (
        <div>
            这是一级路由组件Layout
            <Link to="/about">去about页</Link>
            <Link to="/board">去board页</Link>
            {/* 配置二级路由组件渲染位置 */}
            <Outlet/>
        </div>
    )
}

export default Layout

默认二级路由配置

场景和配置方式:当访问的是一级路由时,默认的二级路由组件可以得到渲染,只需要在二级路由的位置去掉path,设置index属性为true
下例代码解析:当访问localhost:3000时,会渲染Layout组件,同时也会渲染二级组件Board;访问localhost:3000/board时,会报404错误

const router = createBrowserRouter([
    {
        path:'/',
        element: <Layout/>,
        children:[
            // 设置默认二级路由:一级路由访问时,默认二级路由组件也会渲染
            {
                index: true,
                element: <Board/>
            },
            {
                path: '/about',
                element: <About/>
            }
        ]
    }
])

image.png

404路由配置

场景:当浏览器输入url的路径在整个路由配置中都找不到对应的path,为了用户体验,可以使用404兜底组件进行渲染
实现步骤:

  1. 准备一个NotFound组件
  2. 在路由表数组的末尾,以*号作为路由path配置路由
const router = createBrowserRouter([
    ...code
  
    // 配置404页面
    {
        path:'*',
        element: <NotFound/>
    }
])

Hash和History路由模式

常用的路由模式有两种,history模式和hash模式,ReactRouter分别由createBrowerRouter(history路由)createHashRouter(hash路由)函数负责创建

路由模式url表现底层原理是否需要后端支持
historyurl/loginhistory对象 + pushState事件需要
hashurl/#/login监听hashChange事件不需要
const router = createHashRouter([
    {
        path: '/login',
        element: <Login />
    },
    {
        path: '/article/:id',
        element: <Article />
    },
])
const router = createBrowserRouter([
    {
        path: '/login',
        element: <Login />
    },
    {
        path: '/article/:id',
        element: <Article />
    }
])

useReducer

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

// 1. 定义一个reducer函数(根据不同的action返回不同的新状态)
function reducer(state, action) {
  // 根据不同的action type 返回新的state
  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函数和状态的初始值
  const [state, dispatch] = useReducer(reducer, 0);

  return (
    <div>
      this is App
      {/* 3. 通过dispatch函数分派一个action对象(通知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;

useMemo - 不常用

作用:在组件每次重新渲染的时候缓存计算的结果
用途:一般用于消耗非常大的计算
说明:使用useMemo做缓存之后可以保证只有count1依赖项发生变化时才会重新计算
语法

useMemo(() => {
  // 根据count1返回计算的结果
}, [count1])

解析:当我们点击按钮改变count1和count2时,组件App会重新渲染,故fib(count1)会重新执行

import { useMemo, useState } from "react";

function fib(n) {
  console.log("计算函数执行了");
  if (n < 3) {
    return 1;
  }
  return fib(n - 1) + fib(n - 2);
}

function App() {
  const [count1, setCount1] = useState(0);
  const result = fib(count1);
  const [count2, setCount2] = useState(0);

  console.log("组件重新渲染");
  return (
    <div>
      this is App
      <button onClick={() => setCount1(count1 + 1)}>
        change count1: {count1}
      </button>
      <button onClick={() => setCount2(count2 + 1)}>
        change count2: {count2}
      </button>
      {result}
    </div>
  );
}

export default App;

image.png
解析:当我们点击按钮改变count1和count2时,组件App会重新渲染,但由于使用useMemoAPI,且依赖项为count1,所以仅在count1发生变化时执行fib函数

import { useMemo, useState } from "react";

function fib(n) {
  console.log("计算函数执行了");
  if (n < 3) {
    return 1;
  }
  return fib(n - 1) + fib(n - 2);
}

function App() {
  const [count1, setCount1] = useState(0);
  const result = useMemo(() => {
    return fib(count1);
  }, [count1]);
  const [count2, setCount2] = useState(0);

  console.log("组件重新渲染");
  return (
    <div>
      this is App
      <button onClick={() => setCount1(count1 + 1)}>
        change count1: {count1}
      </button>
      <button onClick={() => setCount2(count2 + 1)}>
        change count2: {count2}
      </button>
      {result}
    </div>
  );
}

export default App;

image.png

React.memo

作用:运行组件在Props没有改变的情况下跳过渲染
React组件默认的渲染机制:只要父组件重新渲染子组件就重新渲染
说明:经过memo函数包裹生成的缓存组件只有在props发生变化的时候才会重新渲染
语法

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

解析:点击按钮改变count时,App组件会重新渲染,Son组件也会重新被渲染;但Son组件没有必要重新渲染,因为没有变化

import { useState } from "react";

function Son() {
  console.log("son子组件重新渲染了");
  return <div>this is son</div>;
}

function App() {
  const [count, setCount] = useState(0);
  return (
    <div>
      this is App
      <button onClick={() => setCount(count + 1)}>+</button>
      {count}
      <Son />
    </div>
  );
}

export default App;

解析:点击按钮改变count时,App组件会重新渲染,MemoSon组件不会重新渲染,因为通过memo包裹的组件在props没有发生变化时,是不会重新渲染的

import { memo, useState } from "react";

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

function App() {
  const [count, setCount] = useState(0);
  return (
    <div>
      this is App
      <button onClick={() => setCount(count + 1)}>+</button>
      {count}
      <MemoSon />
    </div>
  );
}

export default App;

React.memo - props的比较机制

机制:在使用memo缓存组件之后,React会对每一个prop使用Object.is比较新值和老值,返回true,表示没有变化
prop是简单类型:Object.is(3,3) => true 没有变化
prop引用类型(对象/数组):Object([],[])=>false 有变化,React只关心引用是否变化
注意:引用类型中,变量其实是该引用类型值在内存中的地址,所以如果地址没有变化,也就代表prop没有变化
解析:传递一个简单类型prop,点击按钮App组件会重新渲染,子组件MemoSon不会重新渲染,因为num没有发生变化

import { memo, useState } from "react";

const MemoSon = memo(function Son({ count }) {
  console.log("son子组件重新渲染了");
  return <div>this is son -- {count}</div>;
});

function App() {
  const [count, setCount] = useState(0);
  console.log("App重新渲染");
  const num = 10;
  return (
    <div>
      {count}
      <button onClick={() => setCount(count + 1)}>+</button>
      <MemoSon count={num} />
    </div>
  );
}

export default App;

解析:传递一个引用类型prop,点击按钮App组件会重新渲染,子组件MemoSon也会重新渲染,看似list变量没有发生变化,实际上在App组件重新渲染时,会在内存中新建一个[0,1,2]值的引用地址,即list的值的引用地址发生了改变,所以导致MemoSon组件重新渲染

import { memo, useState } from "react";

const MemoSon = memo(function Son({ list }) {
  console.log("son子组件重新渲染了");
  return <div>this is son -- {list}</div>;
});

function App() {
  const [count, setCount] = useState(0);
  console.log("App重新渲染");
  const list = [0, 1, 2];
  return (
    <div>
      {count}
      <button onClick={() => setCount(count + 1)}>+</button>
      <MemoSon list={list} />
    </div>
  );
}

export default App;

useCallback

作用:使用useCallback包裹函数之后,函数可以保证在App组件重新渲染的时候保持引用稳定
语法:

const func = useCallback(() => {
    // ...
}, []);

解析:点击button后App会重新渲染,当App重新渲染时,由于changeHandler是函数(引用类型),引用地址会发生变化,且作为prop传递给了Input组件,故Input组件也会重新渲染

import { memo, useState } from "react";

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

function App() {
  console.log("App重新渲染");
  const [count, setCount] = useState(0);
  // 当App重新渲染时,由于changeHandler函数(引用类型)引用地址会发生变化,
  // 且作为prop传递给了Input组件,故Input组件也会重新渲染
  const changeHandler = (value) => {
    console.log(value);
  };
  return (
    <div>
      <Input onChange={changeHandler} />
      <button onClick={() => setCount(count + 1)}>{count}</button>
    </div>
  );
}

export default App;

解决:通过useCallback包裹函数,保持引用稳定

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

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

function App() {
  console.log("App重新渲染");
  const [count, setCount] = useState(0);
  // 通过useCallback包裹函数保持引用稳定,在App重新渲染之后,
  // changehandler引用地址保持稳定,不会发生变化
  const changeHandler = useCallback((value) => {
    console.log(value);
  }, []);
  return (
    <div>
      <Input onChange={changeHandler} />
      <button onClick={() => setCount(count + 1)}>{count}</button>
    </div>
  );
}

export default App;

React - forwardRef

作用:在父组件中通过ref获取子组件内部的元素
语法forwardRefapi中的ref是固定写法、位置

const Input = forwardRef((props, ref) => {
  return <input type="text" ref={ref} />;
});

const inputRef = useRef(null);
<Input ref={inputRef} />
inputRef.current.focus()

比如
image.png

import { forwardRef, useRef } from "react";

const Input = forwardRef((props, ref) => {
  return <input type="text" ref={ref} />;
});

function App() {
  const inputRef = useRef(null);

  const getInput = () => {
    console.log(inputRef.current.focus());
  };
  return (
    <div>
      <Input ref={inputRef} />
      <button onClick={getInput}>获取input</button>
    </div>
  );
}

export default App;

React - useImperativeHandle

作用:通过ref调用子组件内部的方法
语法:

const Component = forwardRef((props, ref) => {
  const func = () => {
    // ...
  };
  useImperativeHandle(ref, () => {
    return {
      func,
    };
  });
  return <div></div>
});

const sonRef = useRef(null);
<Component ref={sonRef} />
sonRef.current.func();

比如
image.png

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

const Son = forwardRef((props, ref) => {
  const inputRef = useRef(null);

  const focusHandler = () => {
    inputRef.current.focus();
  };

  useImperativeHandle(ref, () => {
    return {
      focusHandler,
    };
  });

  return <input type="text" ref={inputRef} />;
});

function App() {
  const sonRef = useRef(null);

  const focusHandler = () => {
    sonRef.current.focusHandler();
  };
  return (
    <div>
      <Son ref={sonRef} />
      <button onClick={focusHandler}>聚焦</button>
    </div>
  );
}

export default App;

Class类组件(不推荐,官方停止更新,推荐使用Hooks写法)

基础结构

  1. 通过类属性state定义状态数据
  2. 通过setState方法来修改状态数据
  3. 通过render来写UI模板(JSX语法一致)
  4. 在类组件中十分依赖this关键字,如访问state、调用事件等
class Counter extends Component {
  // 定义状态变量   类似于    useState()
  state = {
    count: 0,
  };

  // 事件回调
  clickHandler = () => {
    // 修改状态数据
    this.setState({
      count: this.state.count + 1,
    });
  };

  // UI模板(JSX)
  render() {
    return <button onClick={this.clickHandler}>{this.state.count}</button>;
  }
}

export default Counter

生命周期函数

概念:组件从创建到销毁的各个阶段自动执行的函数就是生命周期函数

  1. componentDidMount:组件挂载完毕自动执行,一般用于异步数据获取等
  2. componentWillUnmount:组件卸载时自动执行,一般用于清除副作用等,如清除定时器
import { Component, useState } from "react";

class Son extends Component {
  // 挂载
  componentDidMount() {
    console.log("son componentDidMount");
    // 设置定时器
    this.timer = setInterval(() => {
      console.log("son timer");
    }, 1000);
  }

  // 卸载
  componentWillUnmount() {
    console.log("son componentWillUnmount");
    // 卸载定时器
    clearInterval(this.timer);
  }

  render() {
    return <div>this is Son</div>;
  }
}

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

export default App;

组件通信

概念:类组件和Hooks编写的组件在组件通信的思想完全一致

  1. 父传子:通过prop绑定数据
  2. 子传父:通过prop绑定父组件中的函数,子组件调用
  3. 兄弟通信:状态提升,通过父组件做桥接
import { Component } from "react";

class Son extends Component {
  render() {
    return (
      <div>
        this is Son - {this.props.name}
        <button onClick={() => this.props.onGetSonName("wangwu")}>
          wangwu
        </button>
      </div>
    );
  }
}

class Parent extends Component {
  state = {
    name: "zhangsan",
  };

  getSonName = (sonName) => {
    this.setState({
      name: sonName,
    });
  };
  render() {
    return (
      <div>
        this is parent
        <Son name={this.state.name} onGetSonName={this.getSonName} />
      </div>
    );
  }
}

export default Parent;

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2061187.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

STM32f407 网络接收 fpga 的 bin 文件并更新到 fpga series7(3)

STM32f407 网络接收 fpga 的 bin 文件并更新到 fpga series7(3) 简介 实验 3&#xff1a;在搭建好 tcp 服务器&#xff0c;并拟定好协议的前提下&#xff0c;接收每一个 bin 文件的块&#xff0c;配置到 fpga。 原理图 fpga fpga1 stm32 接线总结 // fpga引脚 stm32…

快速了解矿用电源特性及其性能测试利器电源ate检测系统

在矿产资源开采的每一个环节&#xff0c;矿用电源都扮演着幕后英雄的角色&#xff0c;它的作用不可小觑。那么什么是矿用电源呢&#xff1f;电源ate检测系统如何助力矿用电源性能测试呢&#xff1f; 矿用电源模块介绍 矿用电源是专门用于矿井等地下作业场所的重要电源设备&…

阿里MAXCOMPUTE数据专辑信息读取并同步数据表

阿里MAXCOMPUTE数据专辑信息读取并同步数据表 在阿里云大数据体系中&#xff0c;我们可以使用数据地图的数据专辑&#xff0c;对数据的类别等进行一个管理 那么管理后的数据&#xff0c;我们想要落表进行相关的数据分析&#xff0c;如何做呢&#xff1f; 查看阿里云官方文档…

虚幻5|制作刀光粒子效果

一&#xff0c;创建一个粒子效果 1.Niagara系统 2.右键添加发射器&#xff0c;创建一个空白 3.点击空白的渲染&#xff0c;选择条带渲染器 4.右侧选择自定义侧面矢量 5.按顺序如下&#xff0c;编辑刀光的周期和方向 6.添加一个spawn per frame&#xff0c;使刀光每帧都在生成&…

Upload-Lab第13关:POST上传方式如何巧妙利用%00截断法绕过上传验证

第13关概述 在Upload-Lab第13关中&#xff0c;服务器会对上传的文件进行严格的扩展名检查。只有符合白名单的扩展名&#xff08;如.jpg、.png等&#xff09;才能成功上传。我们的目标是绕过这种检查&#xff0c;将恶意文件&#xff08;如.php&#xff09;上传到服务器。以下是…

图神经网络教程4-卷积图神经网络

介绍 卷积神经网络在涉及图像的预测任务上取得了最先进的性能。通过将权值学习核与输入图像卷积&#xff0c;CNN根据其视觉外观提取感兴趣的特征&#xff0c;无论它们在图像中的位置是哪里。虽然图像只是图的一个特殊情况(见图1 (a))&#xff0c;但是为图领域定义一个广义卷积…

了解同步带选择同步带

同步带和轮选型 同步带传动属于皮带传动&#xff0c;但是改进了传统皮带传动无法保持严格的传动比的打滑问题&#xff0c;传统皮带传动依靠皮带和皮带轮张紧时产生的摩擦力传输动力&#xff0c;但是从动轮遇到障碍或超载荷时&#xff0c;皮带会在皮带轮产生滑动。 解决打滑问题…

企业高性能web服务器【Nginx详解】

一.Web 服务基础介绍 1.1 互联网发展历程 1993年3月2日&#xff0c;中国科学院高能物理研究所租用AT&T公司的国际卫星信道建立的接入美国SLAC国家实 验室的64K专线正式开通&#xff0c;成为我国连入Internet的第一根专线。 1995年马云开始创业并推出了一个web网站 中国黄页…

【其它-高效处理小技巧】如何批量备份263企业邮箱邮件

如何批量备份263企业邮箱邮件 近期由于有人离职&#xff0c;邮箱要注销&#xff0c;之前邮箱内有5000多封沟通邮件&#xff0c;为避免将来找不到沟通过程&#xff0c;所以需要备份。 目的&#xff1a;一次性备份所有沟通邮件 方法一&#xff1a; 少于20封邮件&#xff0c;推荐…

基于vue框架的爱心公益网站532y9(程序+源码+数据库+调试部署+开发环境)系统界面在最后面。

系统程序文件列表 项目功能&#xff1a;用户,志愿者,公益资讯,捐赠物资,公益项目,项目报名,公益类型 开题报告内容 基于Vue框架的爱心公益网站 开题报告 一、项目背景与意义 在快速发展的现代社会中&#xff0c;公益事业作为社会文明进步的重要标志&#xff0c;越来越受到…

创建GPTs,打造你的专属AI聊天机器人

在2023年11月的「OpenAI Devday」大会上&#xff0c;OpenAI再度带来了一系列令人瞩目的新功能&#xff0c;其中ChatGPT方面的突破尤为引人关注。而GPTs的亮相&#xff0c;不仅标志着个性化AI时代的到来&#xff0c;更为开发者和普通用户提供了前所未有的便利。接下来&#xff0…

WPS又崩了,在黑神话中挤出一条热搜!

吉祥知识星球http://mp.weixin.qq.com/s?__bizMzkwNjY1Mzc0Nw&mid2247485367&idx1&sn837891059c360ad60db7e9ac980a3321&chksmc0e47eebf793f7fdb8fcd7eed8ce29160cf79ba303b59858ba3a6660c6dac536774afb2a6330#rd 《网安面试指南》http://mp.weixin.qq.com/s?…

idea中如何不重启tomcat 即可看到修改内容变化

我 | 在这里 ⭐ 全栈开发攻城狮、全网10W粉丝、2022博客之星后端领域Top1、专家博主。 &#x1f393;擅长 指导毕设 | 论文指导 | 系统开发 | 毕业答辩 | 系统讲解等。已指导60位同学顺利毕业。持续接单中。。。 ✈️个人公众号&#xff1a;热爱技术的小郑。回复 Java全套视频教…

基于ElementPlus的分页表格组件ReTable

分页表格ReTable 组件实现基于 Vue3 Element Plus Typescript&#xff0c;同时引用 vueUse lodash-es tailwindCss (不影响功能&#xff0c;可忽略) 基于ElTable和ElPagination组件封装的分页表格&#xff0c;支持本地分页以及远程请求两种方式。本地数据分页自带全量数据的…

QT聊天室基于Tcp

server.cpp #include "widget.h" #include "ui_widget.h"Widget::Widget(QWidget *parent): QWidget(parent), ui(new Ui::Widget),server(new QTcpServer(this)) // 给服务器指针对象实例化空间{ui->setupUi(this); }Widget::~Widget() {delete ui; }…

集团数字化转型方案(一)

集团数字化转型方案通过系统集成先进的物联网&#xff08;IoT&#xff09;、大数据分析、人工智能&#xff08;AI&#xff09;和云计算技术&#xff0c;构建一个全面智能化的运营生态系统&#xff0c;涵盖从数据驱动的决策支持、智能化业务流程优化、到全渠道客户体验提升的各个…

【算法基础实验】图论-最小生成树-Prim的即时实现

理论知识 Prim算法是一种用于计算加权无向图的最小生成树&#xff08;MST, Minimum Spanning Tree&#xff09;的贪心算法。最小生成树是一个连通的无向图的子图&#xff0c;它包含所有的顶点且总权重最小。Prim算法从一个起始顶点开始&#xff0c;不断将权重最小的边加入生成…

CTFHUB | web进阶 | JSON Web Token | 无签名

一些JWT库也支持none算法&#xff0c;即不使用签名算法。当alg字段为空时&#xff0c;后端将不执行签名验证 开启题目 账号密码随便输&#xff0c;登录之后显示只有 admin 可以获得 flag 在此页面抓包发到 repeater&#xff0c;这里我们需要用到一个 Burp 插件&#xff0c;按图…

科研绘图配色大全

目录 01 颜色网站 1.1 Material 1.1.1 tailwindcolor 1.2 Trending Color Palettes1.3 Material Palette 1.4 Graphs Colors 1.5 RGB颜色值与十六进制颜色码转换 1.6 colorbrewer 1.7 优设 1.8 Chinese Colors1.9 handpicked colors 02 科研绘图配色方案 2.1 常见科技…

干货:2024必备的四大PDF编辑器推荐!

面对PDF文件的编辑需求&#xff0c;你是否感到无从下手&#xff1f;那么&#xff0c;今天就为大家推荐几款实用的PDF编辑工具&#xff0c;让你轻松应对各种PDF编辑难题。 福昕PDF编辑器 链接&#xff1a;editor.foxitsoftware.cn 福昕PDF编辑器多功能专业级是我PDF编辑器。它…