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 (


    {list.map((item) => (

    姓名:{item.name}


    ))}

    )
    }

条件渲染
  • 根据是否满足条件生成HTML结构,比如Loading效果

  • 可以使用 三元运算符逻辑与(&&)运算符逻辑或(||)运算符

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


    {flag ? '正确的' : null}
    {flag && '前面只有为true的情况下才会执行'}
    {!flag || '前面只有为false的情况下才会执行'}

    )
    }

if系列判断渲染

  • 可以声明一个方法,接收值,内部进行判断,并返回对应的结果

    export default function App() {
    const getType = (type) => {
    if (type === 0) {
    return 111
    } else if (type === 1) {
    return 222
    } else if (type === 2) {
    return 333
    }
    }
    return (


    {getType(1)}

    )
    }

样式处理
  • 利用className指定类名,适合样式比较多的情况

  • 直接行内样式,适合样式比较少的

    标题

  • 单独声明一个样式类名对象

    export default function App() {
    let style = {
    color: 'pink',
    fontSize: 20,
    }
    return (


    标题



    )
    }

动态类名

  • 根据条件显示类名

    export default function App() {
    let style = {
    color: 'pink',
    fontSize: 20,
    }
    let [flag, setFlag] = useState(true)
    return (


    <h2 style={flag ? style : ''}>标题

    )
    }

动态类名插件

  • 还有很多用法,可以查看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 (



    {/* 评论数 /}

    5 评论

    {/
    排序 */}


      {state.tabs.map((item) => {
      return (
      <li
      className={state.active === item.type ? 'on' : ''}
      onClick={() => activeClick('hot')}
      key={item.id}
      >
      按{item.name}排序

      )
      })}

          {/* 添加评论 */}
          <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

  2. 那个组件需要就直接导入,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}>
    {/
    根组件 */}


    App


    <Son></Son>

    </Context.Provider>
    </>
    )
    }

  3. 数据消费组件,用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 }}>

    App -- {y}



)
}

案例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:

    ,
    },
    ])

    export default routes

  2. 在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>
    )

  3. 修改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',
  })
相关推荐
bysking17 分钟前
【前端-组件】定义行分组的表格表单实现-bysking
前端·react.js
王哲晓33 分钟前
第三十章 章节练习商品列表组件封装
前端·javascript·vue.js
理想不理想v38 分钟前
‌Vue 3相比Vue 2的主要改进‌?
前端·javascript·vue.js·面试
酷酷的阿云1 小时前
不用ECharts!从0到1徒手撸一个Vue3柱状图
前端·javascript·vue.js
aPurpleBerry2 小时前
JS常用数组方法 reduce filter find forEach
javascript
ZL不懂前端2 小时前
Content Security Policy (CSP)
前端·javascript·面试
乐闻x2 小时前
ESLint 使用教程(一):从零配置 ESLint
javascript·eslint
我血条子呢3 小时前
[Vue]防止路由重复跳转
前端·javascript·vue.js
半开半落3 小时前
nuxt3安装pinia报错500[vite-node] [ERR_LOAD_URL]问题解决
前端·javascript·vue.js·nuxt