React复习笔记

基础语法

创建项目

借助脚手架,新建一个React项目(可以使用vite或者cra,这里使用cra)

npx create-react-app 项目名

  • create-react-app是React脚手架的名称

启动项目

npm start   或者  yarn start

  • src是源文件
  • index.js相当于Vue的main.js文件。整个程序的入口
  • App.js相当于Vue的App.js,根组件
{}表达式
  • 里面可以写入方法,变量,三元,短路与(&&),短路或(||)等js表达式
  • 表达式是可以产生一个值的js语句(也就是可以被函数返回)
import { useState } from 'react';
import './App.css';

const App = () => {
  const [num, setNum] = useState(100)
  const [flag, setFlag] = useState(true)
  const fn = () => {
    return '方法执行了'
  }

  return (
    <div className="App">
      <h3>{ }中可以使用定义好的变量</h3>
      { num }
      <h3>{ }中可以使用三元表达式</h3>
      { flag ? num : '无' }
      <h3>{ }中可以使用短路与</h3>
      { flag && num }
      <h3>{ }中可以使用短路或</h3>
      { !flag || num }
      <h3>{ }中可以使用方法</h3>
      { fn() }
    </div>
  );
}

export default App;
列表渲染
  • 通过map进行遍历,里面需要绑定key值,方便diff算法进行对比,提高diff性能
  • 重复渲染那个模板,就return
  • key 在当前列表中要唯一的字符串或者数值(String/Number)
export default function App() {
  const [list, setList] = useState([
    { id: 0, name: '张三' },
    { id: 1, name: '李四' },
    { id: 2, name: '王五' },
  ])
  return (
    <div>
      {list.map((item) => (
        <p key={item.id}>姓名:{item.name}</p>
      ))}
    </div>
  )
}
条件渲染
  • 根据是否满足条件生成HTML结构,比如Loading效果
  • 可以使用 三元运算符 或  逻辑与(&&)运算符逻辑或(||)运算符
export default function App() {
  const [flag, setFlag] = useState(true)
  return (
    <div>
      {flag ? '正确的' : null}
      {flag && '前面只有为true的情况下才会执行'}
      {!flag || '前面只有为false的情况下才会执行'}
    </div>
  )
}

if系列判断渲染

  • 可以声明一个方法,接收值,内部进行判断,并返回对应的结果
export default function App() {
  const getType = (type) => {
    if (type === 0) {
      return <span>111</span>
    } else if (type === 1) {
      return <span>222</span>
    } else if (type === 2) {
      return <span>333</span>
    }
  }
  return (
    <div>
      {getType(1)}
    </div>
  )
}
样式处理
  • 利用className指定类名,适合样式比较多的情况
  • 直接行内样式,适合样式比较少的
<h2 style={{ color: 'red' }}>标题</h2>
  • 单独声明一个样式类名对象
export default function App() {
  let style = {
    color: 'pink',
    fontSize: 20,
  }
  return (
    <div>
      <h2 style={style}>标题</h2>
    </div>
  )
}

动态类名

  • 根据条件显示类名
export default function App() {
  let style = {
    color: 'pink',
    fontSize: 20,
  }
  let [flag, setFlag] = useState(true)
  return (
    <div>
      <h2 style={flag ? style : ''}>标题</h2>
    </div>
  )
}

动态类名插件

  • 还有很多用法,可以查看npm搜索

注意事项
  1. JSX必须有一个根节点,如果没有根节点,可以使用<></>(幽灵节点)替代
  2. 所有标签必须形成闭合,成对闭合或者自闭合都可以
  3. JSX中的语法更加贴近JS语法,属性名采用驼峰命名法  class -> className  for -> htmlFor
  4. JSX支持多行(换行),如果需要换行,需使用() 包裹,防止bug出现
小案例1
  • 实现一个最基本的评论(不完整)
import './index.css'
import avatar from './images/avatar.png'
// 依赖的数据
const state = {
  // hot: 热度排序  time: 时间排序
  tabs: [
    {
      id: 1,
      name: '热度',
      type: 'hot',
    },
    {
      id: 2,
      name: '时间',
      type: 'time',
    },
  ],
  active: 'hot',
  list: [
    {
      id: 1,
      author: '刘德华',
      comment: '给我一杯忘情水',
      time: new Date('2021-10-10 09:09:00'),
      // 1: 点赞 0:无态度 -1:踩
      attitude: 1,
    },
    {
      id: 2,
      author: '周杰伦',
      comment: '哎哟,不错哦',
      time: new Date('2021-10-11 09:09:00'),
      // 1: 点赞 0:无态度 -1:踩
      attitude: 0,
    },
    {
      id: 3,
      author: '五月天',
      comment: '不打扰,是我的温柔',
      time: new Date('2021-10-11 10:09:00'),
      // 1: 点赞 0:无态度 -1:踩
      attitude: -1,
    },
  ],
}
// 时间格式化
const format = (time) => {
  return `${time.getFullYear()}-${
    time.getMonth() + 1 < 10 ? '0' + time.getMonth() + 1 : time.getMonth() + 1
  }-${time.getDate() < 10 ? '0' + time.getDate() : time.getDate()} ${
    time.getHours() < 10 ? '0' + time.getHours() : time.getHours()
  }:${time.getMinutes() < 10 ? '0' + time.getMinutes() : time.getMinutes()}:${
    time.getSeconds() < 10 ? '0' + time.getSeconds() : time.getSeconds()
  }`
}
// tab切换
const activeClick = (active) => {
  state.active = active
}

function App() {
  return (
    <div className="App">
      <div className="comment-container">
        {/* 评论数 */}
        <div className="comment-head">
          <span>5 评论</span>
        </div>
        {/* 排序 */}
        <div className="tabs-order">
          <ul className="sort-container">
            {state.tabs.map((item) => {
              return (
                <li
                  className={state.active === item.type ? 'on' : ''}
                  onClick={() => activeClick('hot')}
                  key={item.id}
                >
                  按{item.name}排序
                </li>
              )
            })}
          </ul>
        </div>

        {/* 添加评论 */}
        <div className="comment-send">
          <div className="user-face">
            <img className="user-head" src={avatar} alt="" />
          </div>
          <div className="textarea-container">
            <textarea
              cols="80"
              rows="5"
              placeholder="发条友善的评论"
              className="ipt-txt"
            />
            <button className="comment-submit">发表评论</button>
          </div>
          <div className="comment-emoji">
            <i className="face"></i>
            <span className="text">表情</span>
          </div>
        </div>

        {/* 评论列表 */}
        <div className="comment-list">
          {state.list.map((item) => {
            return (
              <div className="list-item" key={item.id}>
                <div className="user-face">
                  <img className="user-head" src={avatar} alt="" />
                </div>
                <div className="comment">
                  <div className="user">{item.author}</div>
                  <p className="text">{item.comment}</p>
                  <div className="info">
                    <span className="time">{format(item.time)}</span>
                    <span
                      className={item.attitude === 1 ? 'like liked' : 'like'}
                    >
                      <i className="icon" />
                    </span>
                    <span
                      className={item.attitude === -1 ? 'hate hated' : 'hate'}
                    >
                      <i className="icon" />
                    </span>
                    <span className="reply btn-hover">删除</span>
                  </div>
                </div>
              </div>
            )
          })}
        </div>
      </div>
    </div>
  )
}

export default App

组件

  • 分为函数式组件(rfc)和类组件(rnc)
  • 安装ES7+ React/Redux/React-Native snippets这个插件后就可以使用上述指令快速创建组件
  • 主要讲函数式组件。在react中,一个组件就是首字母大写的函数
绑定事件
  • on开头,后面紧跟事件名(事件名首字母大写)

on事件名 = { 事件处理函数名 }      // 无参

on事件名 = { () => 事件处理函数名(参数1,参数2...) }      // 有参

on事件名 = { (e) => 事件处理函数名(e,参数2...) }      // 有参,带e的

  • 事件处理函数

let/const  事件处理函数名  =  (参数) => { ... }

import React from 'react'

export default function App() {
  const print = () => {
    console.log('无参的')
  }
  const hasParams = (e, num) => {
    console.log('有参的', e, num)
  }

  return (
    <div>
      <button onClick={print}>print</button>
      <button onClick={(e) => hasParams(e, '123')}>hasParams</button>
    </div>
  )
}
小技巧
  • 数值改变
  • 数组添加
  • 对象修改
import React from 'react'
import { useState } from 'react'

export default function App() {
  const [num, setNum] = useState(10)
  const [list, setList] = useState([])
  const [obj, setObj] = useState({
    name: '张三',
  })

  // 数值加几,可以直接在后面写加几
  const numAdd = (n) => {
    setNum(num + n)
  }
  // 数组添加,可以直接在尾部添加
  const listAdd = (item) => {
    setList([...list, item])
  }
  // 修改对象中的某一项
  const objEdit = (val) => {
    setObj({
      ...obj,
      // 下面的会覆盖上面的同名的属性,达到修改的目的
      name: val,
    })
  }

  return (
    <div>
      <button onClick={() => numAdd(1)}>数值加1--{num}</button>
      <div>
        <button onClick={() => listAdd('数组新的一项')}>数组添加一项</button>
        {list.map((item, i) => (
          <p key={i}>{item}</p>
        ))}
      </div>
      <div>
        <button onClick={() => objEdit('李四')}>
          修改对象的某一项(修改name)
        </button>
        <p>{obj.name}</p>
      </div>
    </div>
  )
}
  • 数组删除(最好利用filter)
import React from 'react'
import { useState } from 'react'

export default function App() {
  const [list, setList] = useState([1,2,3])
  // 删除数组中下标为2的内一项
  const delItem = (index) => {
    let newList = list.filter((item, i) => i !== 2)
    setList(newList)
    // 或者直接操作,也是可以的
    // setList(list.filter((item, i) => i !== 2))
  }

  return (
    <div>
      <button onClick={() => delItem(2)}>删除数组中的某一项</button>
    </div>
  )
}
受控组件
  • 被react状态控制的组件就叫受控组件。通过事件对象e,可以获取输入框中的值
import React from 'react'
import { useState } from 'react'

export default function App() {
  const [val, setVal] = useState('')
  // 表单里面的值发生变化
  const onChange = (e) => {
    // 获得输入框中的值
    console.log(e.target.value)
    // 赋值给val
    setVal(e.target.value)
  }

  return (
    <div>
      <input type="text" name="" id="" value={val} onChange={onChange} />
    </div>
  )
}
非受控组件
  • 不受react状态控制的组件叫非受控组件。通过获取dom元素,来获取输入框中的值
import React from 'react'
import { useRef } from 'react'

export default function App() {
  const ipt = useRef(null)
  // 表单里面的值发生变化
  const onChange = () => {
    // 获得输入框中的值
    console.log(ipt.current.value)
  }

  return (
    <div>
      <input type="text" name="" id="" ref={ipt} onChange={onChange} />
    </div>
  )
}
小案例2
  • 完整的评论功能
import './index.css'
import avatar from './images/avatar.png'
import { useState } from 'react'

// 时间格式化
const format = (time) => {
  return `${time.getFullYear()}-${
    time.getMonth() + 1 < 10 ? '0' + time.getMonth() + 1 : time.getMonth() + 1
  }-${time.getDate() < 10 ? '0' + time.getDate() : time.getDate()} ${
    time.getHours() < 10 ? '0' + time.getHours() : time.getHours()
  }:${time.getMinutes() < 10 ? '0' + time.getMinutes() : time.getMinutes()}:${
    time.getSeconds() < 10 ? '0' + time.getSeconds() : time.getSeconds()
  }`
}

function App() {
  // hot: 热度排序  time: 时间排序
  const [tabs] = useState([
    {
      id: 1,
      name: '热度',
      type: 'hot',
    },
    {
      id: 2,
      name: '时间',
      type: 'time',
    },
  ])
  const [list, setList] = useState([
    {
      id: 1,
      author: '刘德华',
      comment: '给我一杯忘情水',
      time: new Date('2021-10-10 09:09:00'),
      // 1: 点赞 0:无态度 -1:踩
      attitude: 1,
    },
    {
      id: 2,
      author: '周杰伦',
      comment: '哎哟,不错哦',
      time: new Date('2021-10-11 09:09:00'),
      // 1: 点赞 0:无态度 -1:踩
      attitude: 0,
    },
    {
      id: 3,
      author: '五月天',
      comment: '不打扰,是我的温柔',
      time: new Date('2021-10-11 10:09:00'),
      // 1: 点赞 0:无态度 -1:踩
      attitude: -1,
    },
  ])
  // 切换的tab
  const [active, setActive] = useState('hot')
  // tab切换
  const activeClick = (type) => {
    setActive(type)
  }
  // 输入框的值
  const [iptVal, setIptVal] = useState('')
  // 得到输入框中的值
  const getVal = (e) => {
    setIptVal(e.target.value)
  }
  // 点击发送评论按钮
  const sendCommit = () => {
    if (!iptVal || iptVal.trim().length < 1) {
      return alert('输入不能为空或都是空格')
    }
    setList([
      ...list,
      {
        id: +new Date(),
        author: '孤勇者',
        comment: iptVal,
        time: new Date(),
        // 1: 点赞 0:无态度 -1:踩
        attitude: 0,
      },
    ])
    setIptVal('')
  }
  // 点击删除
  const delItm = (id) => {
    let newList = list.filter((item) => item.id !== id)
    setList(newList)
  }
  // 点击点赞/点踩
  const toggleMood = (item) => {
    let { id, attitude } = item
    let newList = list.map((item) => {
      if (item.id === id) {
        return {
          ...item,
          attitude: attitude === 1 ? 0 : 1,
        }
      } else {
        return item
      }
    })
    console.log(newList)
    setList(newList)
  }

  return (
    <div className="App">
      <div className="comment-container">
        {/* 评论数 */}
        <div className="comment-head">
          <span>{list.length} 评论</span>
        </div>
        {/* 排序 */}
        <div className="tabs-order">
          <ul className="sort-container">
            {tabs.map((item) => {
              return (
                <li
                  className={active === item.type ? 'on' : ''}
                  onClick={() => activeClick(item.type)}
                  key={item.id}
                >
                  按{item.name}排序
                </li>
              )
            })}
          </ul>
        </div>

        {/* 添加评论 */}
        <div className="comment-send">
          <div className="user-face">
            <img className="user-head" src={avatar} alt="" />
          </div>
          <div className="textarea-container">
            <textarea
              cols="80"
              rows="5"
              placeholder="发条友善的评论"
              className="ipt-txt"
              onChange={getVal}
              value={iptVal}
            />
            <button className="comment-submit" onClick={sendCommit}>
              发表评论
            </button>
          </div>
          <div className="comment-emoji">
            <i className="face"></i>
            <span className="text">表情</span>
          </div>
        </div>

        {/* 评论列表 */}
        <div className="comment-list">
          {list.map((item, index) => {
            return (
              <div className="list-item" key={item.id}>
                <div className="user-face">
                  <img className="user-head" src={avatar} alt="" />
                </div>
                <div className="comment">
                  <div className="user">{item.author}</div>
                  <p className="text">{item.comment}</p>
                  <div className="info">
                    <span className="time">{format(item.time)}</span>
                    <span
                      className={item.attitude === 1 ? 'like liked' : 'like'}
                      onClick={() => toggleMood(item)}
                    >
                      <i className="icon" />
                    </span>
                    <span
                      className={item.attitude === -1 ? 'hate hated' : 'hate'}
                    >
                      <i className="icon" />
                    </span>
                    <span
                      className="reply btn-hover"
                      onClick={() => delItm(item.id)}
                    >
                      删除
                    </span>
                  </div>
                </div>
              </div>
            )
          })}
        </div>
      </div>
    </div>
  )
}

export default App

组件通信

  • 父子通信,子父通信,非父子通信
父->子通信
  • 父组件在子组件标签上绑定要传入的数据(会默认添加到props中),子组件通过props进行使用

父组件

import React from 'react'
import { useState } from 'react'
import Son from './pages/Son.jsx'

export default function App() {
  const [num, setNum] = useState(100)
  return (
    <div>
      <h2>App</h2>
      <p>下面是子组件</p>
      <Son num={num}></Son>
    </div>
  )
}

子组件

import React from 'react'

export default function Son(props) {
  return (
    <div>
      <h2>Son</h2>
      <p>从父组件传来的值是:{props.num}</p>
    </div>
  )
}
props详解
  1. props是只读对象(readonly)
    • 是自顶向下单向数据流,根据单项数据流的要求,子组件只能读取props中的数据,不能进行修改
    • 不同于Vue,react的props比较彻底,就完全不能修改。Vue如果传入对象类型数据,其实是可以修改的
  1. props可以传递任意数据
    • 数字、字符串、布尔值、数组、对象、函数(多用子向父传值)JSX(类似于Vue的插槽)

父组件

import React,{ useState }  from 'react'
import Son from './pages/Son.jsx'

export default function App() {
  // 数字
  const [num, setNum] = useState(100)
  // 字符
  const [str, setStr] = useState('str')
  // 布尔
  const [bool, setBool] = useState(false)
  // 数组
  const [list, setList] = useState([1, 2, 3])
  // 对象
  const [obj, setObj] = useState({ name: '张三', age: 24 })
  // 函数
  const print = () => {
    return 'print'
  }
  // jsx
  const jsx = <span>123</span>

  return (
    <div>
      <h2>App</h2>
      <p>下面是子组件</p>
      <Son
        num={num}
        str={str}
        bool={bool}
        list={list}
        obj={obj}
        print={print}
        jsx={jsx}
      >
        直接写在标签内的jsx结构,会自动传入到props中的children属性里(或者子组件标签上写children属性,一样的效果)
      </Son>
    </div>
  )
}

子组件

import React from 'react'
// 也可以直接在参数这里解构
export default function Son(props) {
  let { num, str, bool, list, obj, print, jsx } = props
  return (
    <div>
      <h2>Son</h2>
      <p>从父组件传来的值是:{num}</p>
      <p>从父组件传来的值是:{str}</p>
      <p>从父组件传来的值是:{bool ? '是true' : '是false'}</p>
      <p>从父组件传来的数组,渲染结果如下</p>
      <ul>
        {list.map((item) => (
          <li key={item}>{item}</li>
        ))}
      </ul>
      <p>从父组件传来的对象,渲染结果如下</p>
      {obj.name} --- {obj.age}
      <p>从父组件传来的函数,渲染结果如下</p>
      {print()}
      <p>父组件传来的jsx结构,如下</p>
      {jsx}
      <p>父组件传过来的jsx结构,如下</p>
      {props.children}
    </div>
  )
}
子->父通信
  • 也是通过props,通过传递函数进行通信
  • 子组件调用父组件传递过来的函数,并且把想要传递的数据当成函数的实参
  • 本质就是子组件调用了父组件传递过来的有参方法,只不过参数是子组件提供,从而达到子向父传值的作用

子组件

import React,{ useState }  from 'react'

export default function Son(props) {
  let [msg, setMsg] = useState('子组件向父组件传递的数据')
  const sendMsg = () => {
    props.getMsg(msg)
  }

  return (
    <div>
      <h2>Son</h2>
      <button onClick={sendMsg}>点击向父组件传值</button>
    </div>
  )
}

父组件

import React,{ useState } from 'react'
import Son from './pages/Son'

export default function App() {
  let [sonData, setSonData] = useState({})
  const getMsg = (val) => {
    console.log(val)
    setSonData({ ...sonData, msg: val })
  }

  return (
    <div>
      <h2>App --- {sonData.msg}</h2>
      <Son getMsg={getMsg}></Son>
    </div>
  )
}
兄弟组件通信
  • 通过状态提升,利用共同的父组件实现兄弟通信
  • 兄弟组件A -> 父组件 -> 兄弟组件B

子组件A

import React, { useState } from 'react'

export default function SonA(props) {
  const [msgA, setMsgA] = useState('兄弟组件A传递的数据')
  const sendB = () => {
    props.getMsgA(msgA)
  }

  return (
    <div>
      <h3>Son1</h3>
      <button onClick={sendB}>点击发送给兄弟组件B</button>
    </div>
  )
}

父组件

import React, { useState } from 'react'
import SonA from './pages/SonA'
import SonB from './pages/SonB'

export default function App() {
  const [msgA, setMsgA] = useState('')
  // 接收A组件传来的值
  const getMsgA = (val) => {
    setMsgA(val)
  }

  return (
    <div>
      <h2>App</h2>
      <SonA getMsgA={getMsgA}></SonA>
      <SonB msgA={msgA}></SonB>
    </div>
  )
}

子组件B

import React from 'react'

export default function SonB(props) {
  return (
    <div>
      <h3>Son2</h3>
      <p>接收兄弟组件B传来的值为:{props.msgA}</p>
    </div>
  )
}
跨组件通信Context
  • 直接在index.js文件中提供数据,则全局都可以使用
    • 适用于只是用1次的静态的数据
  • 如果提供的数据需要维护状态,则写到app.js
  • 只要是嵌套关系,都可以通过这个实现通信

使用步骤

  1. 首先创建一个独立的文件,Context.js
import { createContext } from 'react'

const Context = createContext()

export default Context
  1. 那个组件需要就直接导入,Provider标签包裹根组件,value提供数据(提供的数据比较多,就可以使用对象形式)
import React, { useState } from 'react'
import Son from './pages/Son'
// 1. 引入Context
import Context from './utils/context'

export default function App() {
  const [msg, setMsg] = useState('根组件传递的数据')

  return (
    <>
      {/* 2. 使用Provider包裹上层组件提供数据 */}
      <Context.Provider value={msg}>
        {/* 根组件 */}
        <div>
          <h2>App</h2>
          <Son></Son>
        </div>
      </Context.Provider>
    </>
  )
}
  1. 数据消费组件,用useContext这个hook,或者通过Consumer标签接收显示数据

使用useContext这个hook

import React, { useContext } from 'react'
import Sun from './Sun'
import Context from '../utils/context'

export default function Son() {
  let val = useContext(Context)

  return (
    <div>
      <h3>Son</h3>
      <p>从根组件得到的数据 --- {val}</p>
      <Sun></Sun>
    </div>
  )
}

通过Consumer标签

import React from 'react'
import Context from '../utils/context'

export default function Sun() {
  return (
    <div>
      <h5>Sun</h5>
      <div>
        从根组件得到的数据:
        <Context.Consumer>{(value) => <span>{value}</span>}</Context.Consumer>
      </div>
    </div>
  )
}

组件进阶

children属性
  • 表示该组件的子节点,只要组件内部有子节点,props中就有该属性
  • children属性,类似于插槽。直接写在标签中的内容会填充到children属性上面
  • children可以是普通文本普通标签元素函数 / 对象JSX
  • 如果并列的传入多个,propschildren属性会变成一个数组(就可以直接进行遍历)

父组件

import React from 'react'
import Son from './pages/Son'

export default function App() {
  return (
    <div>
      <h2>App</h2>
      <Son>
        普通文本:666
        <div>普通标签元素</div>
        {/* 函数 */}
        {function fn() {
          console.log('函数打印')
        }}
        {/* JSX结构 */}
        {
          <div>
            <p>{'这是一个普通的jsx结构'}</p>
          </div>
        }
      </Son>
    </div>
  )
}

子组件

import React from 'react'

export default function Son(props) {
  console.log(props)
  return (
    <div>
      <h3>Son</h3>
      <p>{props.children[0]}</p>
      {props.children[1]}
      {props.children[2]()}
      {props.children[3]}
	  <hr />
      {props.children.map((item) => {
        return item
      })}
    </div>
  )
}
props校验
  1. 下载 prop-types 插件 ,并导入prop-types包 yarn add prop-types
  2. 使用 组件名.propsTypes = { } 来给组件的props中的数据添加校验规则
  3. 校验规则通过PropTypes对象来指定

检验基本语法

组件名.prototype = {
        属性名 :  PropTypes.XXX,
}

  • PropTypes是引入的prop-types插件的实例

设置默认值

组件名.defaultProps= {
        属性名 :  默认值,
}

  • 或者在参数上直接给默认值

父组件

import React, { useState } from 'react'
import Son from './pages/Son'

export default function App() {
  const [list] = useState([
    {
      id: 0,
      name: '张三',
    },
    {
      id: 1,
      name: '李四',
    },
  ])
  const [obj] = useState({
    name: '王五',
    age: 24,
  })

  return (
    <div>
      <h2>App</h2>
      <Son list={list} score={100} obj={obj}></Son>
    </div>
  )
}

子组件

import React from 'react'
import PropTypes from 'prop-types'

export default function Son(props) {
  let { list, score, obj } = props

  return (
    <div>
      <h3>Son</h3>
      <ul>
        {list.map((item) => (
          <li key={item.id}>{item.name}</li>
        ))}
      </ul>
      <p>成绩是:{score}</p>
      <p>姓名:{obj.name}</p>
      <p>年龄:{obj.age}</p>
    </div>
  )
}

// 对传过来的值进行校验
Son.propTypes = {
  list: PropTypes.array.isRequired,
  // 也可以自定义校验规则  peops是所有接收过来的数据,propsName是字段名,componentName组件名
  score: function (props, propsName, componentName) {
    if (props[propsName] < 60) {
      return new Error('成绩不合格')
    }
  },
  obj: PropTypes.shape({
    name: PropTypes.string,
    age: PropTypes.number,
  }),
}

// 设置默认值
Son.defaultProps = {
  list: [],
  score: 100,
}

常见规则

  • 常见类型: array  bool  func  number  object   string
  • React元素类型(JSX): element
  • 是否必填: isRequired
  • 特定结构的对象: shape({ })  也就是指定对象里面字段的规则,可以指定一个,也可以指定多个
// 特定结构的对象
obj: PropTypes.shape({
  name: PropTypes.string,
  age: PropTypes.number,
}),
  • 也可以自定义校验规则(见上面的例子)

Hook

useState
  • useState(初始值)返回值是一个数组(里面有两项)
  • [数据,修改数据的方法]  是对 useState进行结构。把里面的两项分别结构出来
  • 对于对象类型的状态变量,应该始终传给set方法一个全新的对象(深度拷贝)来进行修改

格式:

let/const  [ 数据 ,修改数据的方法 ] = useState(默认值)

import React from 'react'
import { useState } from 'react'

export default function App() {
  const [count, setCount] = useState(0)
  const [obj,setObj] = useState({ name: 'zs', age: 25 })
  console.log(useState(0)) // (2) [0, ƒ]
  const add = (num) => {
    let newCount = count + num
    setCount(newCount)
  }
  const changeObj = () => {
    setObj({ ...obj, age: 26 })
  }

  return (
    <div>
      <h2>App --- {count} -- {obj}</h2>
      <button onClick={() => add(1)}>+1</button>
      <button onClick={changeObj}>修改对象属性</button>
    </div>
  )
}

函数做为参数

  • useState  中也可以传入一个函数做为参数(初始值可能需要经过一些计算而得)
  • 参数只会在组件的初始渲染中起作用,后续渲染时会被忽略。如果初始 state 需要通过计算才能获得,则可以传入一个函数,在函数中计算并返回初始的 state,此函数只在初始渲染时被调用

格式:

const [name, setName] = useState(()=>{

        // 编写计算逻辑    return '计算之后的初始值'

})

父组件

import React from 'react'
import { useState } from 'react'
import Son from './pages/Son'

export default function App() {
  const [count, setCount] = useState(0)
  const countEdit = (num) => {
    setCount(num)
  }

  return (
    <div>
      <h2>App</h2>
      <button onClick={() => countEdit(10)}>10</button>
      <button onClick={() => countEdit(20)}>20</button>
      <Son count={count}></Son>
    </div>
  )
}

子组件

import React from 'react'
import { useEffect } from 'react'
import { useState } from 'react'

export default function Son(props) {
  const [c, setc] = useState(() => props.count)
  useEffect(() => {
    setc(props.count)
  }, [props])

  return (
    <div>
      <h3>Son -- {c}</h3>
    </div>
  )
}
useEffect

  • useEffect函数的作用就是为react函数组件提供副作用处理的
  • useEffect都是在组件dom渲染更新完毕之后才执行的

副作用

副作用是相对于主作用来说的,一个函数除了主作用,其他的作用就是副作用。对于 React 组件来说,主作用就是根据数据(state/props)渲染 UI,除此之外都是副作用(比如,手动修改 DOM,ajax请求)

常见的副作用

  1. 数据请求 ajax发送
  2. 手动修改dom
  3. localstorage操作

执行时机

1.不添加依赖项

  • 组件首次渲染执行一次,以及不管是哪个状态更改引起组件更新时都会重新执行
    1. 组件初始渲染
    2. 组件更新 (状态数据变化引起的重新渲染,不管结构中使用没使用这个数据状态)
useEffect(()=>{
    console.log('副作用执行了')
})
  1. 添加空数组
  • 组件只在首次渲染时执行一次
useEffect(()=>{
	 console.log('副作用执行了')
},[])
  1. 添加特定依赖项
  • 副作用函数在首次渲染时执行在依赖项发生变化时重新执行
    1. 组件初始渲染
    2. 依赖项发生变化时
function App() {  
    const [count, setCount] = useState(0)  
    const [name, setName] = useState('zs') 
    
    useEffect(() => {    
        console.log('副作用执行了')  
    }, [count])  
    
    return (    
        <>      
         <button onClick={() => { setCount(count + 1) }}>{count}</button>      
         <button onClick={() => { setName('cp') }}>{name}</button>    
        </>  
    )
}

注意事项

  • useEffect 回调函数中用到的状态数据(比如,count)就是依赖数据,就应该出现在依赖项数组中,如果不添加依赖项就会有bug出现

清除副作用

  • 在组件被销毁时,如果有些副作用操作需要被清除(比如定时器)

语法:

useEffect(() => {

        // 副作用操作...

        return () => {

               // 写清除副作用的代码

           }

})

eg:  清除定时器案例

父组件

import React from 'react'
import { useState } from 'react'
import Son from './pages/Son'

export default function App() {
  const [flag, setFlag] = useState(true)

  return (
    <div>
      <h2>App</h2>
      <button onClick={() => setFlag(!flag)}>显示/隐藏组件</button>
      {flag && <Son></Son>}
    </div>
  )
}

子组件

import React, { useEffect } from 'react'

export default function Son() {
  // 组件进来的时候触发一个定时器
  useEffect(() => {
    let timer = setInterval(() => {
      console.log('定时器执行了')
    }, 1000)
    // 组件销毁时清除定时器
    return () => {
      // 在return里面的函数里写清除操作
      clearInterval(timer)
    }
  }, [])
  return (
    <div>
      <h3>Son</h3>
    </div>
  )
}

useEffect 发送网络请求

  • 依赖项要是一个空数组,因为依赖项为空数组时只会在页面初始化时触发一次
import React from 'react'
import { useEffect } from 'react'

export default function App() {
  const getData = () => {
    fetch('https://cnodejs.org/api/v1/topics')
      .then((response) => response.json())
      .then((data) => console.log(data.data))
  }
  useEffect(() => {
    getData()
  }, [])

  return (
    <div>
      <h2>App</h2>
    </div>
  )
}
案例1
  • 求卷去头部距离的hook
import { useState } from 'react'

export default function useWindowScroll() {
  const [y, sety] = useState('')
  window.addEventListener('scroll', function () {
    sety(this.document.documentElement.scrollTop)
  })
  return [y]
}

使用
import React from 'react'
import useWindowScroll from './hook/useWindowScroll'

export default function App() {
  const [y] = useWindowScroll()
  return (
    <div style={{ height: 1600 }}>
      <h2>App -- {y}</h2>
    </div>
  )
}
案例2
  • 数据改变,会同步到本地
import { useEffect, useState } from 'react'

export default function useLocalStorage(key, defaultVal) {
  const [val, setVal] = useState(defaultVal)
  // 只要val发生变化,就同步到本地
  useEffect(() => {
    localStorage.setItem(key, val)
  }, [val, key])
  return [val, setVal]
}

使用
import React from 'react'
import useWindowScroll from './hook/useWindowScroll'
import useLocalStorage from './hook/useLocalStorage'

export default function App() {
  const [y] = useWindowScroll()
  const [val, setVal] = useLocalStorage('val', 0)
  const add = () => {
    setVal(val + 1)
  }

  return (
    <div style={{ height: 1600 }}>
      <h2>
        App -- {y} -- {val}
      </h2>
      <button onClick={add}>+1</button>
    </div>
  )
}
useRef
  • 可以获取元素的真实Dom
import React, { useEffect, useRef } from 'react'

export default function App() {
  const ipt = useRef(null)
  useEffect(() => {
    console.log(ipt.current.value)
  }, [])

  return (
    <div>
      <h2>App</h2>
      <input type="text" ref={ipt} />
    </div>
  )
}
useContext
  • 传输的数据是响应式的,跨组件传输数据用
  • 如果传递的数据,只需要在整个应用初始化的时候传递一次就可以,则可以在index.js文件中提供数据
  • 如果传递的数据需要状态维护,则可以在app.js中提供数据

使用步骤

  1. 创建一个context的文件
  2. 使用createContext创建Context对象,并导出
  3. 在顶层组件引入,通过Provider提供数据
  4. 在底层组件引入,通过useContext函数获取数据

举例

context.js

import { createContext } from 'react'

const Context = createContext()

export default Context

上层组件

import React, { useState } from 'react'
import Son from './pages/Son'
// 1. 引入Context
import Context from './utils/context.js'

export default function App() {
  const [msg] = useState('根组件传递的数据')

  return (
    <>
      {/* 2. 使用Provider包裹上层组件提供数据 */}
      <Context.Provider value={msg}>
        {/* 根组件 */}
        <div>
          <h2>App</h2>
          <Son></Son>
        </div>
      </Context.Provider>
    </>
  )
}

下层组件

import React, { useContext } from 'react'
import Context from '../utils/context.js'

export default function Son() {
  let val = useContext(Context)

  return (
    <div>
      <h3>Son</h3>
      <p>从根组件得到的数据 --- {val}</p>
    </div>
  )
}
hook使用总结

补充

  • document.title 可以获取网页最左上的标题

ReactTookit

首先进行安装toolkitreact-redux

npm install @reduxjs/toolkit react-redux

Redux Toolkit 示例

src目录下新建stroe文件夹,store文件夹下新建index.jsmodules(模块)文件夹

src >> stroe >> index.js / mudules

1. 创建 Redux Store(redux仓库)
  • 在我们新建的store文件夹下的index.js里粘贴即可
import { configureStore } from '@reduxjs/toolkit'

// 使用configureStore创建一个redux仓库
// 并自动配置了 Redux DevTools 扩展 ,这样你就可以在开发时调试 store
export default configureStore({
  // 此时我们还没有写入  reducer  后面这里再写入,有一个就写一个,也可以写入多个,在reducer大括号里写入
  reducer: {},
})
2. 为 React 提供 Redux Store
  • 新建完仓库之后,并没有与React产生关联,所以我们需要产生关联,这样React才能进行使用Toolkit

index.js中进行关联

import React from 'react'
import ReactDOM from 'react-dom'
import './index.css'
import App from './App'
// 引入数据仓库,只有文件夹,默认会引入文件夹下的index.js
import store from './store'
// 结构出提供者组件,它有一个stroe属性,属性值就是我们要传递的值
import { Provider } from 'react-redux'

ReactDOM.render(
  {/* 包裹App组件,这样全局都能进行使用 */}
  <Provider store={store}>
    <App />
  </Provider>,
  document.getElementById('root')
)
3. 创建 Redux State Slice(切片)
  • 切片可以理解为模块
  • modules文件夹下新建counterSlice.js
import { createSlice } from '@reduxjs/toolkit'

// 创建react数据切片 利用createSlice()
export const counterSlice = createSlice({
  // 类似于vuex的命名空间,必须是唯一值
  // 与pinia的defineStore()的第一个参数一个意思,都是唯一值,做区分
  name: 'counter',
  // 定义变量
  initialState: {
    value: 0,
  },
  // 定义方法
  reducers: {
    // 方法接收2个参数,第一个参数是变量,第二个参数是载荷(也就是使用方法传入的参数)
    // +1  
    increment: (state) => {
      // Redux Toolkit 允许我们在 reducers 写 "可变" 逻辑。它
      // 并不是真正的改变状态值,因为它使用了 Immer 库
      // 可以检测到“草稿状态“ 的变化并且基于这些变化生产全新的
      // 不可变的状态
      // 大致意思就是可以直接修改原先的值,它会产生新的不可变状态,放心大胆修改
      state.value += 1
    },
    // -1
    decrement: (state) => {
      state.value -= 1
    },
    // 这种是使用action的时候传参的
    incrementByAmount: (state, action) => {
      state.value += action.payload
    },
  },
})
// 每个 case reducer 函数会生成对应的 Action creators 需要进行导出,这样组件使用方法或者变量的时候,直接引入就可了
export const { increment, decrement, incrementByAmount } = counterSlice.actions

export default counterSlice.reducer
4. 将 Slice Reducers 添加到 Store 中

下一步,我们需要从计数切片中引入 reducer 函数,并将它添加到我们的 store 中。通过在 reducer 参数中定义一个字段,我们告诉 store 使用这个 slice reducer 函数来处理对该状态的所有更新

import { configureStore } from '@reduxjs/toolkit'
// 引入 reducer 函数
import counterSlice from './modules/counterSlice'

// 使用configureStore创建一个redux仓库
// 并自动配置了 Redux DevTools 扩展 ,这样你就可以在开发时调试 store
export default configureStore({
  reducer: {
    // 告诉 store 使用这个 slice reducer 函数来处理对该状态的所有更新
    counter: counterSlice,
  },
})
5. 在 React 组件中使用 Redux 状态和操作

现在我们可以使用 React-Redux 钩子让 React 组件与 Redux store 交互。我们可以使用 useSelector 从 store 中读取数据,使用 useDispatch dispatch actions。

import React from 'react'
import { useSelector, useDispatch } from 'react-redux'
import { decrement, increment } from '../store/modules/counterSlice'

export default function Person() {
  const count = useSelector((state) => state.counter.value)
  const dispatch = useDispatch()

  return (
    <div>
      <h2>Person -- {count}</h2>
      <button
        // aria-label="Increment value"
        onClick={() => dispatch(increment())}
      >
        增加
      </button>
      <button
        // aria-label="Decrement value"
        onClick={() => dispatch(decrement())}
      >
        减少
      </button>
    </div>
  )
}
如何处理异步任务

counterSlice.js

import { createSlice } from '@reduxjs/toolkit'

// 创建react数据切片 利用createSlice()
export const counterSlice = createSlice({
  // 类似于vuex的命名空间,必须是唯一值
  // 与pinia的defineStore()的第一个参数一个意思,都是唯一值,做区分
  name: 'counter',
  // 变量
  initialState: {
    value: 0,
  },
  // 方法
  reducers: {
    // 方法接收2个参数,第一个参数是变量,第二个参数是载荷(也就是使用方法传入的参数)
    increment: (state) => {
      // Redux Toolkit 允许我们在 reducers 写 "可变" 逻辑。它
      // 并不是真正的改变状态值,因为它使用了 Immer 库
      // 可以检测到“草稿状态“ 的变化并且基于这些变化生产全新的
      // 不可变的状态
      state.value += 1
    },
    decrement: (state) => {
      state.value -= 1
    },
    incrementByAmount: (state, action) => {
      state.value += action.payload
    },
    // 获取数据的异步请求方法
    getD: (state, action) => {
      fetch('https://cnodejs.org/api/v1/topics')
        .then((response) => response.json())
        .then((res) => console.log(res))
    },
  },
})
// 每个 case reducer 函数会生成对应的 Action creators
export const { increment, decrement, incrementByAmount, getD } =
  counterSlice.actions

// 定义异步任务,参数写道外层函数上,利用闭包
export const getData = (payload) => {
  // 内层函数,第一个参数是提交任务的dispatch,第二个参数是获取state的方法
  return (dispatch, getState) => {
    // 获取state中的数据
    console.log(getState().counter)
    // 调用方法
    dispatch(getD())
  }
}

export default counterSlice.reducer

使用

import React from 'react'
import { useSelector, useDispatch } from 'react-redux'
import { decrement, increment, getData } from '../store/modules/counterSlice'

export default function Person() {
  const count = useSelector((state) => state.counter.value)
  const dispatch = useDispatch()

  return (
    <div>
      <h2>Person -- {count}</h2>
      <button onClick={() => dispatch(increment())}>增加</button>
      <button onClick={() => dispatch(decrement())}>减少</button>
      <button onClick={() => dispatch(getData())}>获取数据</button>
    </div>
  )
}

结果

路由

实现一个简单路由

  1. 先进行安装

npm i react-router-dom

  1. 创建page文件夹,然后新建需要的页面

  1. 创建router文件夹,新建index.js,然后在index.js中写入路由规则
import { createBrowserRouter } from 'react-router-dom'
import Login from '../page/Login'
import Article from '../page/Article'
import Home from '../page/Home'

// 使用的浏览器路由模式
const routes = createBrowserRouter([
  {
    path: '/',
    element: <Home />,
  },
  {
    path: '/login',
    element: <Login />,
  },
  {
    path: '/article',
    element: <Article />,
  },
])

export default routes
  1. 在index.js中,引入路由规则和路由注入方法,并进行配置
import React from 'react'
import ReactDOM from 'react-dom/client'
import './index.css'
// 引入路由规则数组
import routes from './router'
// 引入路由注入方法
import { RouterProvider } from 'react-router-dom'

// 引入数据仓库,只有文件夹,默认会引入文件夹下的index.js
import store from './store'
// 结构出提供者组件,它有一个stroe属性,属性值就是我们要传递的值
import { Provider } from 'react-redux'

const root = ReactDOM.createRoot(document.getElementById('root'))
root.render(
  <React.StrictMode>
    <Provider store={store}>
      <RouterProvider router={routes}></RouterProvider>
    </Provider>
  </React.StrictMode>
)
  1. 修改app.js(不修改也没事,直接删除也行,反正一进来就匹配的Home页面)
export default function App() {
  return <></>
}

路由跳转

  • 分为声明式导航和编程式导航
声明式导航
  • 通过Link标签的to属性指定跳转的路由path
import { useSelector, useDispatch } from 'react-redux'
import { decrement, increment } from '../../store/modules/counterSlice'
import { Link } from 'react-router-dom'

export default function Home() {
  const count = useSelector((state) => state.counter.value)
  const dispatch = useDispatch()

  return (
    <div>
      <h2>Person -- {count}</h2>
      <button onClick={() => dispatch(increment())}>增加</button>
      <button onClick={() => dispatch(decrement())}>减少</button>
      <Link to="/login">跳转到登录页</Link>
      <Link to="/article">跳转到文章</Link>
    </div>
  )
}
编程式导航
  • 通过useNavigate这个方法来进行跳转
import { useSelector, useDispatch } from 'react-redux'
import { decrement, increment } from '../../store/modules/counterSlice'
import { Link, useNavigate } from 'react-router-dom'

export default function Home() {
  const count = useSelector((state) => state.counter.value)
  const dispatch = useDispatch()

  const navigate = useNavigate()

  const toLogin = () => {
    navigate('/login')
  }

  return (
    <div>
      <h2>Person -- {count}</h2>
      <button onClick={() => dispatch(increment())}>增加</button>
      <button onClick={() => dispatch(decrement())}>减少</button>
      <Link to="/login">跳转到登录页</Link>
      <button onClick={() => navigate('/article')}>跳转到文章页</button>
      <button onClick={toLogin}>跳转到登录页</button>
      {/* 下面这样写是不行的 */}
      {/* <button onClick={navigate('/login')}>跳转到登录页</button> */}
    </div>
  )
}

路由传参

  • 可以在路由后面使用?拼接参数。也可以使用/路径参数形式
?形式拼接传参

传递参数

import { useSelector, useDispatch } from 'react-redux'
import { decrement, increment } from '../../store/modules/counterSlice'
import { Link, useNavigate } from 'react-router-dom'

export default function Home() {
  const count = useSelector((state) => state.counter.value)
  const dispatch = useDispatch()

  const navigate = useNavigate()

  return (
    <div>
      <h2>Person -- {count}</h2>
      <button onClick={() => dispatch(increment())}>增加</button>
      <button onClick={() => dispatch(decrement())}>减少</button>
      <Link to="/login?name=zs&age=25">跳转到登录页</Link>
      <button onClick={() => navigate('/article?name=ls&age=26')}>
        跳转到文章页
      </button>
    </div>
  )
}

结果:会显示在地址栏上

接收参数

import React from 'react'
import { Outlet, useSearchParams } from 'react-router-dom'

export default function Article() {
  const [params] = useSearchParams()
  const name = params.get('name')

  return (
    <div>
      文章页面{name} <Outlet></Outlet>
    </div>
  )
}
/路径参数传参

传递参数

import { useSelector, useDispatch } from 'react-redux'
import { decrement, increment } from '../../store/modules/counterSlice'
import { Link, useNavigate } from 'react-router-dom'

export default function Home() {
  const count = useSelector((state) => state.counter.value)
  const dispatch = useDispatch()

  const navigate = useNavigate()

  return (
    <div>
      <h2>Person -- {count}</h2>
      <button onClick={() => dispatch(increment())}>增加</button>
      <button onClick={() => dispatch(decrement())}>减少</button>
      <Link to="/login/100">跳转到登录页</Link>
      <button onClick={() => navigate('/article/100/1')}>跳转到文章页</button>
    </div>
  )
}

也要修改下路由配置,改为动态路由形式

import { createBrowserRouter } from 'react-router-dom'
import Login from '../page/Login'
import Article from '../page/Article'
import Home from '../page/Home'

// 使用的浏览器路由模式
const routes = createBrowserRouter([
  {
    path: '/',
    element: <Home />,
  },
  {
    path: '/login/:id',
    element: <Login />,
  },
  {
    path: '/article/:num/:id',
    element: <Article />,
  },
])

export default routes

结果:会显示在地址栏上

接收参数

Login:

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

export default function Login() {
  const params = useParams()
  const id = params.id
  return <div>登陆页面{id}</div>
}

Article:

import React from 'react'
import { Outlet, useParams } from 'react-router-dom'

export default function Article() {
  const params = useParams()
  const name = params.name
  const id = params.id

  return (
    <div>
      文章页面{id}{name} <Outlet></Outlet>
    </div>
  )
}

嵌套路由

配置嵌套路由

import { createBrowserRouter } from 'react-router-dom'
import Login from '../page/Login'
import Article from '../page/Article'
import Home from '../page/Home'

// 使用的浏览器路由模式
const routes = createBrowserRouter([
  {
    path: '/',
    element: <Home />,
  },
  {
    path: '/login/:id',
    element: <Login />,
    // 嵌套路由
    children: [
      {
        // 不需要带 / 了
        path: 'test',
        element: <Article />,
      },
    ],
  },
  {
    path: '/article/:id/:name',
    element: <Article />,
  },
])

export default routes

测试

配置路由出口:在对应的页面配置

子路由内容就出来了

设置初始默认页面

去除path,然后添加index为true

import { createBrowserRouter } from 'react-router-dom'
import Login from '../page/Login'
import Article from '../page/Article'
import Home from '../page/Home'

// 使用的浏览器路由模式
const routes = createBrowserRouter([
  {
    path: '/',
    element: <Home />,
  },
  {
    path: '/login/:id',
    element: <Login />,
    // 嵌套路由
    children: [
      {
        // 不需要带 / 了
        // path: 'test',
        index: true,
        element: <Article />,
      },
    ],
  },
  {
    path: '/article/:id/:name',
    element: <Article />,
  },
])

export default routes

测试

404页面

  • 一定要放到最后

两种路由模式

Hook补充

useReducer

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

案例

import { useReducer } from 'react'
import { Button } from 'antd'

const Home = () => {
  const fn = (state, action) => {
    // 根据传入的不同类型,来进行对应的数据操作
    switch (action.type) {
      case 'INC':
        return state + 1
      case 'DEC':
        return state - 1
      default:
        return state
    }
  }
  // num是值,dispatchNum是修改状态的方法(参考useState)
  const [num, dispatchNum] = useReducer(fn, 0)
  const changeNum = (params) => {
    dispatchNum(params)
  }

  return (
    <div>
      <h3>{num}</h3>
      <Button onClick={() => changeNum({ type: 'INC' })}>num++</Button>
      <Button onClick={() => changeNum({ type: 'DEC' })}>num--</Button>
    </div>
  )
}

export default Home

useMemo

  • 作用:在组件每次重新渲染的时候缓存计算的结果(缓存的是值,useCallback缓存的是函数)

首先先看一个场景

  • count1与函数相关,所以count1变化的时候会触发函数
  • 但是count2并没有与函数有什么关联,但是count2变化了,同样也会触发函数的执行,以达到视图的更新。但是这样显然是不合理的。此时就可以使用uesMemok来进行优化

基本语法

案例

import React, { useState } from 'react'
import { Button } from 'antd'

export default function Home() {
  const [count1, setCount1] = useState(0)
  const [count2, setCount2] = useState(0)

  console.log('组件重新渲染了')

  const fn = () => {
    console.log('函数调用了' + count1)
  }

  return (
    <div>
      <h2>Home{fn()}</h2>
      <Button onClick={() => setCount1(count1 + 1)}>改变count1,count1++</Button>
      <Button onClick={() => setCount2(count2 + 1)}>改变count2,count2++</Button>
    </div>
  )
}
  • fn函数只与count1有关,所以count1变化的时候触发fn函数是正确的。但是经过测试,会发现count2变化的时候也会触发fn函数,这是不对啊

就可以使用useMome对上面的进行优化

import React, { useMemo, useState } from 'react'
import { Button } from 'antd'

export default function Home() {
  const [count1, setCount1] = useState(0)
  const [count2, setCount2] = useState(0)

  console.log('组件重新渲染了')

  const fn = useMemo(() => {
    console.log('函数调用了' + count1)
  }, [count1])

  return (
    <div>
      {/* 由于缓存的是值,所以调用也需要改下 */}
      <h2>Home{fn}</h2>
      <Button onClick={() => setCount1(count1 + 1)}>改变count1,count1++</Button>
      <Button onClick={() => setCount2(count2 + 1)}>改变count2,count2++</Button>
    </div>
  )
}

React.memo

  • 默认只要父组件有状态更新,子组件就会直接重新渲染,存在性能浪费

测试结果

useMomo基本语法

案例:

测试结果:

React.memo对比机制

  • 传递给子组件基本类型数据,如果没有改变,确实不会重新渲染(可以看上面的案例)
  • 但是传递给子组件复杂数据类型,如果没用useSate修饰的,则不管值变没变,都会重新渲染

测试结果:传递给子组件的count和list并没有改变,但是也触发了子组件渲染

为了解决这个问题,就可以采用useMemo配合React.memo

最终方案

测试结果:

useCallback

  • 与useMemo不同,useCallback缓存的是函数引用,不是值

React.forwardRef

  • 使用ref暴露DOM节点给父组件。可以通过React.forwardRef获取子组件DOM

实现

useImperativeHandle

  • 通过ref暴露子组件中的方法,区别与React.forwardRef(这个是暴露的子组件DOM)

案例

React与Ts

创建项目(使用的vite)

npm create vite@latest 项目名 -- --template react-ts

useState类型

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

泛型参数
  • useState本身是一个泛型函数
import { useState } from 'react'

interface IObj {
  name: string
  age: number
}

function App() {
  const [obj] = useState<IObj>({
    name: 'zs',
    age: 25,
  })

  return (
    <>
      <h2>App</h2>
      {obj.name} -- {obj.age}
    </>
  )
}

export default App

补充,其实setState里也可以传入一个函数,只要函数返回一个值就行

初始值为null
  • 有时候,state的初始值设置为了null。则类型就可以使用联合类型
import { useState } from 'react'

interface IObj {
  name: string
  age: number
}

function App() {
  const [obj] = useState<IObj | null>(null)

  return (
    <>
      <h2>App</h2>
      {obj?.name} -- {obj?.age}
    </>
  )
}

export default App

props类型

案例

import { useState } from 'react'

interface IProps {
  count: number
}

function App() {
  const [count] = useState(100)
  return (
    <>
      <h2>App</h2>
      <Son count={count}></Son>
    </>
  )
}

function Son(props: IProps) {
  return (
    <>
      <h3>子组件 {props.count}</h3>
    </>
  )
}

export default App
为children添加类型

为事件添加类型

案例

import { useState } from 'react'

interface IProps {
  count: number
  children: React.ReactNode
  getMsg?: (str: string) => void
}

function App() {
  const [count] = useState(100)
  const getMsg = (msg: string) => {
    console.log('接收到了子组件的值:', msg)
  }
  return (
    <>
      <h2>App</h2>
      <Son count={count} getMsg={getMsg}>
        传给子组件的内容
      </Son>
    </>
  )
}

function Son(props: IProps) {
  const { getMsg } = props
  const sendMsg = () => {
    getMsg?.('子向父传的值')
  }
  return (
    <>
      <h3>子组件 {props.count}</h3>
      <button onClick={sendMsg}>传给父组件的值</button>
    </>
  )
}

export default App

useRef类型

获取Dom使用

案例

import { useEffect, useRef } from 'react'

function App() {
  const inputRef = useRef<HTMLInputElement>(null)

  useEffect(() => {
    inputRef.current?.focus()
  }, [])

  return (
    <>
      <h2>App</h2>
      <input ref={inputRef}></input>
    </>
  )
}

export default App
当成稳定的存储器使用

axios类型

组合成了一种类型

案例

封装axios

import axios from 'axios'

const requestInstance = axios.create({
  baseURL: 'http://geek.itheima.net/v1_0',
  timeout: 5000,
})

requestInstance.interceptors.request.use(
  (config) => {
    return config
  },
  (error) => {
    return Promise.reject(error)
  }
)

requestInstance.interceptors.response.use(
  (response) => {
    return response
  },
  (error) => {
    return Promise.reject(error)
  }
)

export default requestInstance

apis

import { http } from '@/utils'

// 定义通用泛型接口
interface IResType<T> {
  data: T
  message: string
}
// 定义data类型
interface IObj {
  id: number
  name: string
}
interface IDataType {
  channels: Array<IObj>
}

// 请求频道列表
export const getChannels = () =>
  http.request<IResType<IDataType>>({
    url: '/channels',
  })

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

OC类与对象

OC类与对象 本篇是对上一篇的内容的继续学习。从单例模式开始继续学习 文章目录 单例模式定义应用场景特点单例模式的创建 隐藏与封装理解什么是封装目的访问控制符合成存取方法特性的指示符点语法访问属性 对象初始化便利的初始化方法 类的继承特点语法格式重写父类方法super关…

SimpleDateFormat类.Java

目录 1.1构造方法 1.2格式规则 1.3常用方法 1.4练习1&#xff08; 出生日期&#xff09; 1.5练习2(秒杀活动) java.text.SimpleDateFormat 是日期/时间格式化类&#xff0c;我们通过这个类可以帮我们完成日期和文本之间的转换,也就是可以在Date对象与String对象之间进行来…

机器学习理论基础—集成学习(1)

机器学习理论基础—集成学习 个体与集成 集成学习通过构建并结合多个学习器来完成学习任务&#xff0c;有时也称为多分类系统等。 分类&#xff1a; 根据集成学习中的个体学习器的不同可以分为同质集成&#xff08;集成的学习器相同例如全部是决策树&#xff09;&#xff0c…

上市公司专利数据、专利申请、专利授权和质量指标计算面板数据(1990-2022年)

01、数据简介 专利作为企业创新能力和核心竞争力的体现&#xff0c;越来越受到上市公司的重视。了解上市公司的专利数据、专利申请、专利授权和质量指标计算&#xff0c;有助于投资者更好地评估公司的创新能力和长期发展潜力。 通过分析上市公司的专利数据、专利申请、专利授…

【国标语音对讲】EasyCVR视频汇聚平台海康/大华/宇视摄像头GB28181语音对讲配置

一、背景分析 近年来&#xff0c;国内视频监控应用发展迅猛&#xff0c;系统接入规模不断扩大&#xff0c;涌现了大量平台提供商&#xff0c;平台提供商的接入协议各不相同&#xff0c;终端制造商需要给每款终端维护提供各种不同平台的软件版本&#xff0c;造成了极大的资源浪…

[C++ QT项目实战]----系统实现双击表格某一行,表格数据不再更新,可以查看该行所有信息,选中表更新之后,数据可以继续更新

前言 在需要庞大的数据量的系统中&#xff0c;基于合适的功能对数据进行观察和使用至关重要&#xff0c;本篇在自己项目实战的基础上&#xff0c;基于C QT编程语言&#xff0c;对其中一个数据功能进行分析和代码实现&#xff0c;希望可以有所帮助。一些特殊原因&#xff0c;图片…

回溯-单词搜索

给定一个 m x n 二维字符网格 board 和一个字符串单词 word 。如果 word 存在于网格中&#xff0c;返回 true &#xff1b;否则&#xff0c;返回 false 。 单词必须按照字母顺序&#xff0c;通过相邻的单元格内的字母构成&#xff0c;其中“相邻”单元格是那些水平相邻或垂直相…

压测--混合场景设置

1、设计测试场景 性能测试是通过自动化的测试工具模拟多种正常、峰值以及异常负载条件来对系统的各项性能指标满足需求定义的检验活动。一般有以下场景&#xff1a; 基准场景&#xff1a;单接口少量并发用户下压测&#xff0c;评估单个功能点性能。负载场景&#xff1a;逐步增…

Python实践应用|NC文件读取

import netCDF4 as nc import numpy as np import matplotlib.pyplot as plt# 打开NC文件 nc_file E:/NC_file/air.sig995.2012.nc # 将your_file.nc替换为你的NC文件路径 nc_data nc.Dataset(nc_file, r)# 查看NC文件中包含的变量 print("Variables in the NC file:&q…

免费简单好用的内网穿透工具(ngrok、natapp),微信回调地址配置

B站视频地址 文章目录 Natapp1、登录注册账号、下载软件2、使用2-1、购买隧道、查看token2-2、端口穿透 Ngrok1、登录注册账号、下载软件2、使用2-1、获取并设置 token2-2、使用 3、隧道 微信回调配置1、注册测试公众号2、回调代码3、回调配置 在一些特殊的场景下&#xff0c;需…

C#基础之结构体

结构体 文章目录 1、概念2、基本语法3、示例4、结构体的使用5、访问修饰符6、结构体的构造函数思考1 描述矩形信息思考2 职业名字释放了技能思考3 小怪兽思考4 多个小怪兽思考5 奥特曼打小怪兽 1、概念 结构体是一种一定义变量类型 它是数据和函数的集合&#xff0c;可以在结…

PCIe总线-MPS MRRS RCB参数介绍(四)

1.概述 PCIe总线的存储器写请求、存储器读完成等TLP中含有数据负载&#xff0c;即Data Payload。Data Payload的长度和MPS&#xff08;Max Payload Size&#xff09;、MRRS&#xff08;Max Read Request Size&#xff09;和RCB&#xff08;Read Completion Boundary&#xff0…

计算机存储原理.2

1.主存储器与CPU之间的连接 2.存储器芯片的输入输出信号 3.增加主存的存储字长 3.1位扩展 数据总线的利用成分是不充分的(单块只能读写一位)&#xff0c;为了解决这个问题所以引出了位扩展。 使用多块存储芯片解决这个问题。 3.2字扩展 因为存储器买的是8k*8位的&am…

硬件21、接线端子XH2.54、2.54排针排母、2510接插件、PH2.0、町洋接线端子5.08、ISP接口JTAG插座

XH2.54端子的间距为2.54毫米&#xff0c;2.54排针排母的间距也是2.54mm&#xff0c;2510接插件也是2.54、而PH2.0端子的间距为2.0毫米&#xff0c;町洋接线端子插针间的距离是5.08mm&#xff0c;ISP接口JTAG插座针脚的间距一般也是2.54mm XH2.54 针脚间距为2.54mm 插头 接线…

IIS中搭建.Net Core项目,步骤详解

一、准备服务器 1&#xff09;安装IIS 这个比较简单&#xff0c;百度一下就行 2&#xff09;安装 .NET Core 运行时 下载地址&#xff1a;下载 .NET(Linux、macOS 和 Windows) 因为我是本地开发&#xff0c;所以我下载的是SDK 安装成功之后显示如下&#xff1a; 检查是否安装…

判断字符串由几个单词组成(C语言)

一、N-S流程图&#xff1b; 二、运行结果&#xff1b; 三、源代码&#xff1b; # define _CRT_SECURE_NO_WARNINGS # include <stdio.h>int main() {//初始化变量值&#xff1b;int world 0;int i 0;char c 0;char string[81] { 0 };int num 0;//提示用户&#xff…

使用Github Action实现Hexo博客自动化部署

本文参考自 Akilar&#xff0c;原文地址&#xff1a;https://akilar.top/posts/f752c86d/ 每次部署Hexo都需要运行指令三件套&#xff0c;随着文章越来越多&#xff0c;编译的时间也随之越来越长&#xff0c;通过Github Action&#xff0c;我们只需要在每次完成博客的编写或修…

OSPF路由计算

1.区域内路由计算 &#xff08;1&#xff09;LSA的基本概念 LS Age&#xff1a;当LSA被始发时&#xff0c;该字段为0&#xff0c;随着LSA在网络中被泛洪&#xff0c;该时间逐渐累加&#xff0c;当到达MaxAge&#xff08;缺省值为3600s&#xff09;时&#xff0c;LSA不再用于路…

传统过程自动化工厂的智能扩展

一 通过NOA概念&#xff0c;公开、安全地迈向未来 随着数字化转型在过程自动化工业中的不断深入&#xff0c;许多公司都面临着同一挑战——如何平衡创新和传统。放眼望去&#xff0c;过程自动化工业和信息技术似乎在以不同的速度发展。虽然过程自动化工厂通过使用传统的自动化…

基于springboot实现企业oa管理系统项目【项目源码+论文说明】计算机毕业设计

基于springboot实现企业oa管理系统演示 摘要 随着信息技术在管理上越来越深入而广泛的应用&#xff0c;管理信息系统的实施在技术上已逐步成熟。本文介绍了企业OA管理系统的开发全过程。通过分析企业OA管理系统管理的不足&#xff0c;创建了一个计算机管理企业OA管理系统的方案…