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

项目构建命令

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;
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值