• 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语句(也就是可以被函数返回)
    1. import { useState } from 'react';
    2. import './App.css';
    3. const App = () => {
    4. const [num, setNum] = useState(100)
    5. const [flag, setFlag] = useState(true)
    6. const fn = () => {
    7. return '方法执行了'
    8. }
    9. return (
    10. <div className="App">
    11. <h3>{ }中可以使用定义好的变量</h3>
    12. { num }
    13. <h3>{ }中可以使用三元表达式</h3>
    14. { flag ? num : '无' }
    15. <h3>{ }中可以使用短路与</h3>
    16. { flag && num }
    17. <h3>{ }中可以使用短路或</h3>
    18. { !flag || num }
    19. <h3>{ }中可以使用方法</h3>
    20. { fn() }
    21. </div>
    22. );
    23. }
    24. export default App;
    列表渲染
    • 通过map进行遍历,里面需要绑定key值,方便diff算法进行对比,提高diff性能
    • 重复渲染那个模板,就return
    • key 在当前列表中要唯一的字符串或者数值(String/Number)
    1. export default function App() {
    2. const [list, setList] = useState([
    3. { id: 0, name: '张三' },
    4. { id: 1, name: '李四' },
    5. { id: 2, name: '王五' },
    6. ])
    7. return (
    8. <div>
    9. {list.map((item) => (
    10. <p key={item.id}>姓名:{item.name}</p>
    11. ))}
    12. </div>
    13. )
    14. }
    条件渲染
    • 根据是否满足条件生成HTML结构,比如Loading效果
    • 可以使用 三元运算符 或  逻辑与(&&)运算符逻辑或(||)运算符
    1. export default function App() {
    2. const [flag, setFlag] = useState(true)
    3. return (
    4. <div>
    5. {flag ? '正确的' : null}
    6. {flag && '前面只有为true的情况下才会执行'}
    7. {!flag || '前面只有为false的情况下才会执行'}
    8. </div>
    9. )
    10. }

    if系列判断渲染

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

    动态类名

    • 根据条件显示类名
    1. export default function App() {
    2. let style = {
    3. color: 'pink',
    4. fontSize: 20,
    5. }
    6. let [flag, setFlag] = useState(true)
    7. return (
    8. <div>
    9. <h2 style={flag ? style : ''}>标题</h2>
    10. </div>
    11. )
    12. }

    动态类名插件

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

    注意事项
    1. JSX必须有一个根节点,如果没有根节点,可以使用<>(幽灵节点)替代
    2. 所有标签必须形成闭合,成对闭合或者自闭合都可以
    3. JSX中的语法更加贴近JS语法,属性名采用驼峰命名法  class -> className  for -> htmlFor
    4. JSX支持多行(换行),如果需要换行,需使用() 包裹,防止bug出现
    小案例1
    • 实现一个最基本的评论(不完整)
    1. import './index.css'
    2. import avatar from './images/avatar.png'
    3. // 依赖的数据
    4. const state = {
    5. // hot: 热度排序 time: 时间排序
    6. tabs: [
    7. {
    8. id: 1,
    9. name: '热度',
    10. type: 'hot',
    11. },
    12. {
    13. id: 2,
    14. name: '时间',
    15. type: 'time',
    16. },
    17. ],
    18. active: 'hot',
    19. list: [
    20. {
    21. id: 1,
    22. author: '刘德华',
    23. comment: '给我一杯忘情水',
    24. time: new Date('2021-10-10 09:09:00'),
    25. // 1: 点赞 0:无态度 -1:踩
    26. attitude: 1,
    27. },
    28. {
    29. id: 2,
    30. author: '周杰伦',
    31. comment: '哎哟,不错哦',
    32. time: new Date('2021-10-11 09:09:00'),
    33. // 1: 点赞 0:无态度 -1:踩
    34. attitude: 0,
    35. },
    36. {
    37. id: 3,
    38. author: '五月天',
    39. comment: '不打扰,是我的温柔',
    40. time: new Date('2021-10-11 10:09:00'),
    41. // 1: 点赞 0:无态度 -1:踩
    42. attitude: -1,
    43. },
    44. ],
    45. }
    46. // 时间格式化
    47. const format = (time) => {
    48. return `${time.getFullYear()}-${
    49. time.getMonth() + 1 < 10 ? '0' + time.getMonth() + 1 : time.getMonth() + 1
    50. }-${time.getDate() < 10 ? '0' + time.getDate() : time.getDate()} ${
    51. time.getHours() < 10 ? '0' + time.getHours() : time.getHours()
    52. }:${time.getMinutes() < 10 ? '0' + time.getMinutes() : time.getMinutes()}:${
    53. time.getSeconds() < 10 ? '0' + time.getSeconds() : time.getSeconds()
    54. }`
    55. }
    56. // tab切换
    57. const activeClick = (active) => {
    58. state.active = active
    59. }
    60. function App() {
    61. return (
    62. <div className="App">
    63. <div className="comment-container">
    64. {/* 评论数 */}
    65. <div className="comment-head">
    66. <span>5 评论</span>
    67. </div>
    68. {/* 排序 */}
    69. <div className="tabs-order">
    70. <ul className="sort-container">
    71. {state.tabs.map((item) => {
    72. return (
    73. <li
    74. className={state.active === item.type ? 'on' : ''}
    75. onClick={() => activeClick('hot')}
    76. key={item.id}
    77. >
    78. 按{item.name}排序
    79. </li>
    80. )
    81. })}
    82. </ul>
    83. </div>
    84. {/* 添加评论 */}
    85. <div className="comment-send">
    86. <div className="user-face">
    87. <img className="user-head" src={avatar} alt="" />
    88. </div>
    89. <div className="textarea-container">
    90. <textarea
    91. cols="80"
    92. rows="5"
    93. placeholder="发条友善的评论"
    94. className="ipt-txt"
    95. />
    96. <button className="comment-submit">发表评论</button>
    97. </div>
    98. <div className="comment-emoji">
    99. <i className="face"></i>
    100. <span className="text">表情</span>
    101. </div>
    102. </div>
    103. {/* 评论列表 */}
    104. <div className="comment-list">
    105. {state.list.map((item) => {
    106. return (
    107. <div className="list-item" key={item.id}>
    108. <div className="user-face">
    109. <img className="user-head" src={avatar} alt="" />
    110. </div>
    111. <div className="comment">
    112. <div className="user">{item.author}</div>
    113. <p className="text">{item.comment}</p>
    114. <div className="info">
    115. <span className="time">{format(item.time)}</span>
    116. <span
    117. className={item.attitude === 1 ? 'like liked' : 'like'}
    118. >
    119. <i className="icon" />
    120. </span>
    121. <span
    122. className={item.attitude === -1 ? 'hate hated' : 'hate'}
    123. >
    124. <i className="icon" />
    125. </span>
    126. <span className="reply btn-hover">删除</span>
    127. </div>
    128. </div>
    129. </div>
    130. )
    131. })}
    132. </div>
    133. </div>
    134. </div>
    135. )
    136. }
    137. export default App

    组件

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

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

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

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

    • 事件处理函数

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

    1. import React from 'react'
    2. export default function App() {
    3. const print = () => {
    4. console.log('无参的')
    5. }
    6. const hasParams = (e, num) => {
    7. console.log('有参的', e, num)
    8. }
    9. return (
    10. <div>
    11. <button onClick={print}>print</button>
    12. <button onClick={(e) => hasParams(e, '123')}>hasParams</button>
    13. </div>
    14. )
    15. }
    小技巧
    • 数值改变
    • 数组添加
    • 对象修改
    1. import React from 'react'
    2. import { useState } from 'react'
    3. export default function App() {
    4. const [num, setNum] = useState(10)
    5. const [list, setList] = useState([])
    6. const [obj, setObj] = useState({
    7. name: '张三',
    8. })
    9. // 数值加几,可以直接在后面写加几
    10. const numAdd = (n) => {
    11. setNum(num + n)
    12. }
    13. // 数组添加,可以直接在尾部添加
    14. const listAdd = (item) => {
    15. setList([...list, item])
    16. }
    17. // 修改对象中的某一项
    18. const objEdit = (val) => {
    19. setObj({
    20. ...obj,
    21. // 下面的会覆盖上面的同名的属性,达到修改的目的
    22. name: val,
    23. })
    24. }
    25. return (
    26. <div>
    27. <button onClick={() => numAdd(1)}>数值加1--{num}</button>
    28. <div>
    29. <button onClick={() => listAdd('数组新的一项')}>数组添加一项</button>
    30. {list.map((item, i) => (
    31. <p key={i}>{item}</p>
    32. ))}
    33. </div>
    34. <div>
    35. <button onClick={() => objEdit('李四')}>
    36. 修改对象的某一项(修改name)
    37. </button>
    38. <p>{obj.name}</p>
    39. </div>
    40. </div>
    41. )
    42. }
    • 数组删除(最好利用filter)
    1. import React from 'react'
    2. import { useState } from 'react'
    3. export default function App() {
    4. const [list, setList] = useState([1,2,3])
    5. // 删除数组中下标为2的内一项
    6. const delItem = (index) => {
    7. let newList = list.filter((item, i) => i !== 2)
    8. setList(newList)
    9. // 或者直接操作,也是可以的
    10. // setList(list.filter((item, i) => i !== 2))
    11. }
    12. return (
    13. <div>
    14. <button onClick={() => delItem(2)}>删除数组中的某一项</button>
    15. </div>
    16. )
    17. }
    受控组件
    • 被react状态控制的组件就叫受控组件。通过事件对象e,可以获取输入框中的值
    1. import React from 'react'
    2. import { useState } from 'react'
    3. export default function App() {
    4. const [val, setVal] = useState('')
    5. // 表单里面的值发生变化
    6. const onChange = (e) => {
    7. // 获得输入框中的值
    8. console.log(e.target.value)
    9. // 赋值给val
    10. setVal(e.target.value)
    11. }
    12. return (
    13. <div>
    14. <input type="text" name="" id="" value={val} onChange={onChange} />
    15. </div>
    16. )
    17. }
    非受控组件
    • 不受react状态控制的组件叫非受控组件。通过获取dom元素,来获取输入框中的值
    1. import React from 'react'
    2. import { useRef } from 'react'
    3. export default function App() {
    4. const ipt = useRef(null)
    5. // 表单里面的值发生变化
    6. const onChange = () => {
    7. // 获得输入框中的值
    8. console.log(ipt.current.value)
    9. }
    10. return (
    11. <div>
    12. <input type="text" name="" id="" ref={ipt} onChange={onChange} />
    13. </div>
    14. )
    15. }
    小案例2
    • 完整的评论功能
    1. import './index.css'
    2. import avatar from './images/avatar.png'
    3. import { useState } from 'react'
    4. // 时间格式化
    5. const format = (time) => {
    6. return `${time.getFullYear()}-${
    7. time.getMonth() + 1 < 10 ? '0' + time.getMonth() + 1 : time.getMonth() + 1
    8. }-${time.getDate() < 10 ? '0' + time.getDate() : time.getDate()} ${
    9. time.getHours() < 10 ? '0' + time.getHours() : time.getHours()
    10. }:${time.getMinutes() < 10 ? '0' + time.getMinutes() : time.getMinutes()}:${
    11. time.getSeconds() < 10 ? '0' + time.getSeconds() : time.getSeconds()
    12. }`
    13. }
    14. function App() {
    15. // hot: 热度排序 time: 时间排序
    16. const [tabs] = useState([
    17. {
    18. id: 1,
    19. name: '热度',
    20. type: 'hot',
    21. },
    22. {
    23. id: 2,
    24. name: '时间',
    25. type: 'time',
    26. },
    27. ])
    28. const [list, setList] = useState([
    29. {
    30. id: 1,
    31. author: '刘德华',
    32. comment: '给我一杯忘情水',
    33. time: new Date('2021-10-10 09:09:00'),
    34. // 1: 点赞 0:无态度 -1:踩
    35. attitude: 1,
    36. },
    37. {
    38. id: 2,
    39. author: '周杰伦',
    40. comment: '哎哟,不错哦',
    41. time: new Date('2021-10-11 09:09:00'),
    42. // 1: 点赞 0:无态度 -1:踩
    43. attitude: 0,
    44. },
    45. {
    46. id: 3,
    47. author: '五月天',
    48. comment: '不打扰,是我的温柔',
    49. time: new Date('2021-10-11 10:09:00'),
    50. // 1: 点赞 0:无态度 -1:踩
    51. attitude: -1,
    52. },
    53. ])
    54. // 切换的tab
    55. const [active, setActive] = useState('hot')
    56. // tab切换
    57. const activeClick = (type) => {
    58. setActive(type)
    59. }
    60. // 输入框的值
    61. const [iptVal, setIptVal] = useState('')
    62. // 得到输入框中的值
    63. const getVal = (e) => {
    64. setIptVal(e.target.value)
    65. }
    66. // 点击发送评论按钮
    67. const sendCommit = () => {
    68. if (!iptVal || iptVal.trim().length < 1) {
    69. return alert('输入不能为空或都是空格')
    70. }
    71. setList([
    72. ...list,
    73. {
    74. id: +new Date(),
    75. author: '孤勇者',
    76. comment: iptVal,
    77. time: new Date(),
    78. // 1: 点赞 0:无态度 -1:踩
    79. attitude: 0,
    80. },
    81. ])
    82. setIptVal('')
    83. }
    84. // 点击删除
    85. const delItm = (id) => {
    86. let newList = list.filter((item) => item.id !== id)
    87. setList(newList)
    88. }
    89. // 点击点赞/点踩
    90. const toggleMood = (item) => {
    91. let { id, attitude } = item
    92. let newList = list.map((item) => {
    93. if (item.id === id) {
    94. return {
    95. ...item,
    96. attitude: attitude === 1 ? 0 : 1,
    97. }
    98. } else {
    99. return item
    100. }
    101. })
    102. console.log(newList)
    103. setList(newList)
    104. }
    105. return (
    106. <div className="App">
    107. <div className="comment-container">
    108. {/* 评论数 */}
    109. <div className="comment-head">
    110. <span>{list.length} 评论</span>
    111. </div>
    112. {/* 排序 */}
    113. <div className="tabs-order">
    114. <ul className="sort-container">
    115. {tabs.map((item) => {
    116. return (
    117. <li
    118. className={active === item.type ? 'on' : ''}
    119. onClick={() => activeClick(item.type)}
    120. key={item.id}
    121. >
    122. 按{item.name}排序
    123. </li>
    124. )
    125. })}
    126. </ul>
    127. </div>
    128. {/* 添加评论 */}
    129. <div className="comment-send">
    130. <div className="user-face">
    131. <img className="user-head" src={avatar} alt="" />
    132. </div>
    133. <div className="textarea-container">
    134. <textarea
    135. cols="80"
    136. rows="5"
    137. placeholder="发条友善的评论"
    138. className="ipt-txt"
    139. onChange={getVal}
    140. value={iptVal}
    141. />
    142. <button className="comment-submit" onClick={sendCommit}>
    143. 发表评论
    144. </button>
    145. </div>
    146. <div className="comment-emoji">
    147. <i className="face"></i>
    148. <span className="text">表情</span>
    149. </div>
    150. </div>
    151. {/* 评论列表 */}
    152. <div className="comment-list">
    153. {list.map((item, index) => {
    154. return (
    155. <div className="list-item" key={item.id}>
    156. <div className="user-face">
    157. <img className="user-head" src={avatar} alt="" />
    158. </div>
    159. <div className="comment">
    160. <div className="user">{item.author}</div>
    161. <p className="text">{item.comment}</p>
    162. <div className="info">
    163. <span className="time">{format(item.time)}</span>
    164. <span
    165. className={item.attitude === 1 ? 'like liked' : 'like'}
    166. onClick={() => toggleMood(item)}
    167. >
    168. <i className="icon" />
    169. </span>
    170. <span
    171. className={item.attitude === -1 ? 'hate hated' : 'hate'}
    172. >
    173. <i className="icon" />
    174. </span>
    175. <span
    176. className="reply btn-hover"
    177. onClick={() => delItm(item.id)}
    178. >
    179. 删除
    180. </span>
    181. </div>
    182. </div>
    183. </div>
    184. )
    185. })}
    186. </div>
    187. </div>
    188. </div>
    189. )
    190. }
    191. export default App

    组件通信

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

    父组件

    1. import React from 'react'
    2. import { useState } from 'react'
    3. import Son from './pages/Son.jsx'
    4. export default function App() {
    5. const [num, setNum] = useState(100)
    6. return (
    7. <div>
    8. <h2>App</h2>
    9. <p>下面是子组件</p>
    10. <Son num={num}></Son>
    11. </div>
    12. )
    13. }

    子组件

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

    父组件

    1. import React,{ useState } from 'react'
    2. import Son from './pages/Son.jsx'
    3. export default function App() {
    4. // 数字
    5. const [num, setNum] = useState(100)
    6. // 字符
    7. const [str, setStr] = useState('str')
    8. // 布尔
    9. const [bool, setBool] = useState(false)
    10. // 数组
    11. const [list, setList] = useState([1, 2, 3])
    12. // 对象
    13. const [obj, setObj] = useState({ name: '张三', age: 24 })
    14. // 函数
    15. const print = () => {
    16. return 'print'
    17. }
    18. // jsx
    19. const jsx = <span>123</span>
    20. return (
    21. <div>
    22. <h2>App</h2>
    23. <p>下面是子组件</p>
    24. <Son
    25. num={num}
    26. str={str}
    27. bool={bool}
    28. list={list}
    29. obj={obj}
    30. print={print}
    31. jsx={jsx}
    32. >
    33. 直接写在标签内的jsx结构,会自动传入到props中的children属性里(或者子组件标签上写children属性,一样的效果)
    34. </Son>
    35. </div>
    36. )
    37. }

    子组件

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

    子组件

    1. import React,{ useState } from 'react'
    2. export default function Son(props) {
    3. let [msg, setMsg] = useState('子组件向父组件传递的数据')
    4. const sendMsg = () => {
    5. props.getMsg(msg)
    6. }
    7. return (
    8. <div>
    9. <h2>Son</h2>
    10. <button onClick={sendMsg}>点击向父组件传值</button>
    11. </div>
    12. )
    13. }

    父组件

    1. import React,{ useState } from 'react'
    2. import Son from './pages/Son'
    3. export default function App() {
    4. let [sonData, setSonData] = useState({})
    5. const getMsg = (val) => {
    6. console.log(val)
    7. setSonData({ ...sonData, msg: val })
    8. }
    9. return (
    10. <div>
    11. <h2>App --- {sonData.msg}</h2>
    12. <Son getMsg={getMsg}></Son>
    13. </div>
    14. )
    15. }
    兄弟组件通信
    • 通过状态提升,利用共同的父组件实现兄弟通信
    • 兄弟组件A -> 父组件 -> 兄弟组件B

    子组件A

    1. import React, { useState } from 'react'
    2. export default function SonA(props) {
    3. const [msgA, setMsgA] = useState('兄弟组件A传递的数据')
    4. const sendB = () => {
    5. props.getMsgA(msgA)
    6. }
    7. return (
    8. <div>
    9. <h3>Son1</h3>
    10. <button onClick={sendB}>点击发送给兄弟组件B</button>
    11. </div>
    12. )
    13. }

    父组件

    1. import React, { useState } from 'react'
    2. import SonA from './pages/SonA'
    3. import SonB from './pages/SonB'
    4. export default function App() {
    5. const [msgA, setMsgA] = useState('')
    6. // 接收A组件传来的值
    7. const getMsgA = (val) => {
    8. setMsgA(val)
    9. }
    10. return (
    11. <div>
    12. <h2>App</h2>
    13. <SonA getMsgA={getMsgA}></SonA>
    14. <SonB msgA={msgA}></SonB>
    15. </div>
    16. )
    17. }

    子组件B

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

    使用步骤

    1. 首先创建一个独立的文件,Context.js
    1. import { createContext } from 'react'
    2. const Context = createContext()
    3. export default Context
    1. 那个组件需要就直接导入,Provider标签包裹根组件,value提供数据(提供的数据比较多,就可以使用对象形式)
    1. import React, { useState } from 'react'
    2. import Son from './pages/Son'
    3. // 1. 引入Context
    4. import Context from './utils/context'
    5. export default function App() {
    6. const [msg, setMsg] = useState('根组件传递的数据')
    7. return (
    8. <>
    9. {/* 2. 使用Provider包裹上层组件提供数据 */}
    10. <Context.Provider value={msg}>
    11. {/* 根组件 */}
    12. <div>
    13. <h2>App</h2>
    14. <Son></Son>
    15. </div>
    16. </Context.Provider>
    17. </>
    18. )
    19. }
    1. 数据消费组件,用useContext这个hook,或者通过Consumer标签接收显示数据

    使用useContext这个hook

    1. import React, { useContext } from 'react'
    2. import Sun from './Sun'
    3. import Context from '../utils/context'
    4. export default function Son() {
    5. let val = useContext(Context)
    6. return (
    7. <div>
    8. <h3>Son</h3>
    9. <p>从根组件得到的数据 --- {val}</p>
    10. <Sun></Sun>
    11. </div>
    12. )
    13. }

    通过Consumer标签

    1. import React from 'react'
    2. import Context from '../utils/context'
    3. export default function Sun() {
    4. return (
    5. <div>
    6. <h5>Sun</h5>
    7. <div>
    8. 从根组件得到的数据:
    9. <Context.Consumer>{(value) => <span>{value}</span>}</Context.Consumer>
    10. </div>
    11. </div>
    12. )
    13. }

    组件进阶

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

    父组件

    1. import React from 'react'
    2. import Son from './pages/Son'
    3. export default function App() {
    4. return (
    5. <div>
    6. <h2>App</h2>
    7. <Son>
    8. 普通文本:666
    9. <div>普通标签元素</div>
    10. {/* 函数 */}
    11. {function fn() {
    12. console.log('函数打印')
    13. }}
    14. {/* JSX结构 */}
    15. {
    16. <div>
    17. <p>{'这是一个普通的jsx结构'}</p>
    18. </div>
    19. }
    20. </Son>
    21. </div>
    22. )
    23. }

    子组件

    1. import React from 'react'
    2. export default function Son(props) {
    3. console.log(props)
    4. return (
    5. <div>
    6. <h3>Son</h3>
    7. <p>{props.children[0]}</p>
    8. {props.children[1]}
    9. {props.children[2]()}
    10. {props.children[3]}
    11. <hr />
    12. {props.children.map((item) => {
    13. return item
    14. })}
    15. </div>
    16. )
    17. }
    props校验
    1. 下载 prop-types 插件 ,并导入prop-types包 yarn add prop-types
    2. 使用 组件名.propsTypes = { } 来给组件的props中的数据添加校验规则
    3. 校验规则通过PropTypes对象来指定

    检验基本语法

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

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

    设置默认值

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

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

    父组件

    1. import React, { useState } from 'react'
    2. import Son from './pages/Son'
    3. export default function App() {
    4. const [list] = useState([
    5. {
    6. id: 0,
    7. name: '张三',
    8. },
    9. {
    10. id: 1,
    11. name: '李四',
    12. },
    13. ])
    14. const [obj] = useState({
    15. name: '王五',
    16. age: 24,
    17. })
    18. return (
    19. <div>
    20. <h2>App</h2>
    21. <Son list={list} score={100} obj={obj}></Son>
    22. </div>
    23. )
    24. }

    子组件

    1. import React from 'react'
    2. import PropTypes from 'prop-types'
    3. export default function Son(props) {
    4. let { list, score, obj } = props
    5. return (
    6. <div>
    7. <h3>Son</h3>
    8. <ul>
    9. {list.map((item) => (
    10. <li key={item.id}>{item.name}</li>
    11. ))}
    12. </ul>
    13. <p>成绩是:{score}</p>
    14. <p>姓名:{obj.name}</p>
    15. <p>年龄:{obj.age}</p>
    16. </div>
    17. )
    18. }
    19. // 对传过来的值进行校验
    20. Son.propTypes = {
    21. list: PropTypes.array.isRequired,
    22. // 也可以自定义校验规则 peops是所有接收过来的数据,propsName是字段名,componentName组件名
    23. score: function (props, propsName, componentName) {
    24. if (props[propsName] < 60) {
    25. return new Error('成绩不合格')
    26. }
    27. },
    28. obj: PropTypes.shape({
    29. name: PropTypes.string,
    30. age: PropTypes.number,
    31. }),
    32. }
    33. // 设置默认值
    34. Son.defaultProps = {
    35. list: [],
    36. score: 100,
    37. }

    常见规则

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

    Hook

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

    格式:

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

    1. import React from 'react'
    2. import { useState } from 'react'
    3. export default function App() {
    4. const [count, setCount] = useState(0)
    5. const [obj,setObj] = useState({ name: 'zs', age: 25 })
    6. console.log(useState(0)) // (2) [0, ƒ]
    7. const add = (num) => {
    8. let newCount = count + num
    9. setCount(newCount)
    10. }
    11. const changeObj = () => {
    12. setObj({ ...obj, age: 26 })
    13. }
    14. return (
    15. <div>
    16. <h2>App --- {count} -- {obj}</h2>
    17. <button onClick={() => add(1)}>+1</button>
    18. <button onClick={changeObj}>修改对象属性</button>
    19. </div>
    20. )
    21. }

    函数做为参数

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

    格式:

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

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

    })

    父组件

    1. import React from 'react'
    2. import { useState } from 'react'
    3. import Son from './pages/Son'
    4. export default function App() {
    5. const [count, setCount] = useState(0)
    6. const countEdit = (num) => {
    7. setCount(num)
    8. }
    9. return (
    10. <div>
    11. <h2>App</h2>
    12. <button onClick={() => countEdit(10)}>10</button>
    13. <button onClick={() => countEdit(20)}>20</button>
    14. <Son count={count}></Son>
    15. </div>
    16. )
    17. }

    子组件

    1. import React from 'react'
    2. import { useEffect } from 'react'
    3. import { useState } from 'react'
    4. export default function Son(props) {
    5. const [c, setc] = useState(() => props.count)
    6. useEffect(() => {
    7. setc(props.count)
    8. }, [props])
    9. return (
    10. <div>
    11. <h3>Son -- {c}</h3>
    12. </div>
    13. )
    14. }
    useEffect

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

    副作用

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

    常见的副作用

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

    执行时机

    1.不添加依赖项

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

    注意事项

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

    清除副作用

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

    语法:

    useEffect(() => {

            // 副作用操作...

            return () => {

                   // 写清除副作用的代码

               }

    })

    eg:  清除定时器案例

    父组件

    1. import React from 'react'
    2. import { useState } from 'react'
    3. import Son from './pages/Son'
    4. export default function App() {
    5. const [flag, setFlag] = useState(true)
    6. return (
    7. <div>
    8. <h2>App</h2>
    9. <button onClick={() => setFlag(!flag)}>显示/隐藏组件</button>
    10. {flag && <Son></Son>}
    11. </div>
    12. )
    13. }

    子组件

    1. import React, { useEffect } from 'react'
    2. export default function Son() {
    3. // 组件进来的时候触发一个定时器
    4. useEffect(() => {
    5. let timer = setInterval(() => {
    6. console.log('定时器执行了')
    7. }, 1000)
    8. // 组件销毁时清除定时器
    9. return () => {
    10. //return里面的函数里写清除操作
    11. clearInterval(timer)
    12. }
    13. }, [])
    14. return (
    15. <div>
    16. <h3>Son</h3>
    17. </div>
    18. )
    19. }

    useEffect 发送网络请求

    • 依赖项要是一个空数组,因为依赖项为空数组时只会在页面初始化时触发一次
    1. import React from 'react'
    2. import { useEffect } from 'react'
    3. export default function App() {
    4. const getData = () => {
    5. fetch('https://cnodejs.org/api/v1/topics')
    6. .then((response) => response.json())
    7. .then((data) => console.log(data.data))
    8. }
    9. useEffect(() => {
    10. getData()
    11. }, [])
    12. return (
    13. <div>
    14. <h2>App</h2>
    15. </div>
    16. )
    17. }
    案例1
    • 求卷去头部距离的hook
    1. import { useState } from 'react'
    2. export default function useWindowScroll() {
    3. const [y, sety] = useState('')
    4. window.addEventListener('scroll', function () {
    5. sety(this.document.documentElement.scrollTop)
    6. })
    7. return [y]
    8. }
    9. 使用
    10. import React from 'react'
    11. import useWindowScroll from './hook/useWindowScroll'
    12. export default function App() {
    13. const [y] = useWindowScroll()
    14. return (
    15. <div style={{ height: 1600 }}>
    16. <h2>App -- {y}h2>
    17. div>
    18. )
    19. }
    案例2
    • 数据改变,会同步到本地
    1. import { useEffect, useState } from 'react'
    2. export default function useLocalStorage(key, defaultVal) {
    3. const [val, setVal] = useState(defaultVal)
    4. // 只要val发生变化,就同步到本地
    5. useEffect(() => {
    6. localStorage.setItem(key, val)
    7. }, [val, key])
    8. return [val, setVal]
    9. }
    10. 使用
    11. import React from 'react'
    12. import useWindowScroll from './hook/useWindowScroll'
    13. import useLocalStorage from './hook/useLocalStorage'
    14. export default function App() {
    15. const [y] = useWindowScroll()
    16. const [val, setVal] = useLocalStorage('val', 0)
    17. const add = () => {
    18. setVal(val + 1)
    19. }
    20. return (
    21. <div style={{ height: 1600 }}>
    22. <h2>
    23. App -- {y} -- {val}
    24. </h2>
    25. <button onClick={add}>+1</button>
    26. </div>
    27. )
    28. }
    useRef
    • 可以获取元素的真实Dom
    1. import React, { useEffect, useRef } from 'react'
    2. export default function App() {
    3. const ipt = useRef(null)
    4. useEffect(() => {
    5. console.log(ipt.current.value)
    6. }, [])
    7. return (
    8. <div>
    9. <h2>App</h2>
    10. <input type="text" ref={ipt} />
    11. </div>
    12. )
    13. }
    useContext
    • 传输的数据是响应式的,跨组件传输数据用
    • 如果传递的数据,只需要在整个应用初始化的时候传递一次就可以,则可以在index.js文件中提供数据
    • 如果传递的数据需要状态维护,则可以在app.js中提供数据

    使用步骤

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

    举例

    context.js

    1. import { createContext } from 'react'
    2. const Context = createContext()
    3. export default Context

    上层组件

    1. import React, { useState } from 'react'
    2. import Son from './pages/Son'
    3. // 1. 引入Context
    4. import Context from './utils/context.js'
    5. export default function App() {
    6. const [msg] = useState('根组件传递的数据')
    7. return (
    8. <>
    9. {/* 2. 使用Provider包裹上层组件提供数据 */}
    10. <Context.Provider value={msg}>
    11. {/* 根组件 */}
    12. <div>
    13. <h2>App</h2>
    14. <Son></Son>
    15. </div>
    16. </Context.Provider>
    17. </>
    18. )
    19. }

    下层组件

    1. import React, { useContext } from 'react'
    2. import Context from '../utils/context.js'
    3. export default function Son() {
    4. let val = useContext(Context)
    5. return (
    6. <div>
    7. <h3>Son</h3>
    8. <p>从根组件得到的数据 --- {val}</p>
    9. </div>
    10. )
    11. }
    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里粘贴即可
    1. import { configureStore } from '@reduxjs/toolkit'
    2. // 使用configureStore创建一个redux仓库
    3. // 并自动配置了 Redux DevTools 扩展 ,这样你就可以在开发时调试 store
    4. export default configureStore({
    5. // 此时我们还没有写入 reducer 后面这里再写入,有一个就写一个,也可以写入多个,在reducer大括号里写入
    6. reducer: {},
    7. })
    2. 为 React 提供 Redux Store
    • 新建完仓库之后,并没有与React产生关联,所以我们需要产生关联,这样React才能进行使用Toolkit

    index.js中进行关联

    1. import React from 'react'
    2. import ReactDOM from 'react-dom'
    3. import './index.css'
    4. import App from './App'
    5. // 引入数据仓库,只有文件夹,默认会引入文件夹下的index.js
    6. import store from './store'
    7. // 结构出提供者组件,它有一个stroe属性,属性值就是我们要传递的值
    8. import { Provider } from 'react-redux'
    9. ReactDOM.render(
    10. {/* 包裹App组件,这样全局都能进行使用 */}
    11. <Provider store={store}>
    12. <App />
    13. </Provider>,
    14. document.getElementById('root')
    15. )
    3. 创建 Redux State Slice(切片)
    • 切片可以理解为模块
    • modules文件夹下新建counterSlice.js
    1. import { createSlice } from '@reduxjs/toolkit'
    2. // 创建react数据切片 利用createSlice()
    3. export const counterSlice = createSlice({
    4. // 类似于vuex的命名空间,必须是唯一值
    5. // 与pinia的defineStore()的第一个参数一个意思,都是唯一值,做区分
    6. name: 'counter',
    7. // 定义变量
    8. initialState: {
    9. value: 0,
    10. },
    11. // 定义方法
    12. reducers: {
    13. // 方法接收2个参数,第一个参数是变量,第二个参数是载荷(也就是使用方法传入的参数)
    14. // +1
    15. increment: (state) => {
    16. // Redux Toolkit 允许我们在 reducers 写 "可变" 逻辑。它
    17. // 并不是真正的改变状态值,因为它使用了 Immer 库
    18. // 可以检测到“草稿状态“ 的变化并且基于这些变化生产全新的
    19. // 不可变的状态
    20. // 大致意思就是可以直接修改原先的值,它会产生新的不可变状态,放心大胆修改
    21. state.value += 1
    22. },
    23. // -1
    24. decrement: (state) => {
    25. state.value -= 1
    26. },
    27. // 这种是使用action的时候传参的
    28. incrementByAmount: (state, action) => {
    29. state.value += action.payload
    30. },
    31. },
    32. })
    33. // 每个 case reducer 函数会生成对应的 Action creators 需要进行导出,这样组件使用方法或者变量的时候,直接引入就可了
    34. export const { increment, decrement, incrementByAmount } = counterSlice.actions
    35. export default counterSlice.reducer
    4. 将 Slice Reducers 添加到 Store 中

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

    1. import { configureStore } from '@reduxjs/toolkit'
    2. // 引入 reducer 函数
    3. import counterSlice from './modules/counterSlice'
    4. // 使用configureStore创建一个redux仓库
    5. // 并自动配置了 Redux DevTools 扩展 ,这样你就可以在开发时调试 store
    6. export default configureStore({
    7. reducer: {
    8. // 告诉 store 使用这个 slice reducer 函数来处理对该状态的所有更新
    9. counter: counterSlice,
    10. },
    11. })
    5. 在 React 组件中使用 Redux 状态和操作

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

    1. import React from 'react'
    2. import { useSelector, useDispatch } from 'react-redux'
    3. import { decrement, increment } from '../store/modules/counterSlice'
    4. export default function Person() {
    5. const count = useSelector((state) => state.counter.value)
    6. const dispatch = useDispatch()
    7. return (
    8. <div>
    9. <h2>Person -- {count}</h2>
    10. <button
    11. // aria-label="Increment value"
    12. onClick={() => dispatch(increment())}
    13. >
    14. 增加
    15. </button>
    16. <button
    17. // aria-label="Decrement value"
    18. onClick={() => dispatch(decrement())}
    19. >
    20. 减少
    21. </button>
    22. </div>
    23. )
    24. }
    如何处理异步任务

    counterSlice.js

    1. import { createSlice } from '@reduxjs/toolkit'
    2. // 创建react数据切片 利用createSlice()
    3. export const counterSlice = createSlice({
    4. // 类似于vuex的命名空间,必须是唯一值
    5. // 与pinia的defineStore()的第一个参数一个意思,都是唯一值,做区分
    6. name: 'counter',
    7. // 变量
    8. initialState: {
    9. value: 0,
    10. },
    11. // 方法
    12. reducers: {
    13. // 方法接收2个参数,第一个参数是变量,第二个参数是载荷(也就是使用方法传入的参数)
    14. increment: (state) => {
    15. // Redux Toolkit 允许我们在 reducers 写 "可变" 逻辑。它
    16. // 并不是真正的改变状态值,因为它使用了 Immer 库
    17. // 可以检测到“草稿状态“ 的变化并且基于这些变化生产全新的
    18. // 不可变的状态
    19. state.value += 1
    20. },
    21. decrement: (state) => {
    22. state.value -= 1
    23. },
    24. incrementByAmount: (state, action) => {
    25. state.value += action.payload
    26. },
    27. // 获取数据的异步请求方法
    28. getD: (state, action) => {
    29. fetch('https://cnodejs.org/api/v1/topics')
    30. .then((response) => response.json())
    31. .then((res) => console.log(res))
    32. },
    33. },
    34. })
    35. // 每个 case reducer 函数会生成对应的 Action creators
    36. export const { increment, decrement, incrementByAmount, getD } =
    37. counterSlice.actions
    38. // 定义异步任务,参数写道外层函数上,利用闭包
    39. export const getData = (payload) => {
    40. // 内层函数,第一个参数是提交任务的dispatch,第二个参数是获取state的方法
    41. return (dispatch, getState) => {
    42. // 获取state中的数据
    43. console.log(getState().counter)
    44. // 调用方法
    45. dispatch(getD())
    46. }
    47. }
    48. export default counterSlice.reducer

    使用

    1. import React from 'react'
    2. import { useSelector, useDispatch } from 'react-redux'
    3. import { decrement, increment, getData } from '../store/modules/counterSlice'
    4. export default function Person() {
    5. const count = useSelector((state) => state.counter.value)
    6. const dispatch = useDispatch()
    7. return (
    8. <div>
    9. <h2>Person -- {count}</h2>
    10. <button onClick={() => dispatch(increment())}>增加</button>
    11. <button onClick={() => dispatch(decrement())}>减少</button>
    12. <button onClick={() => dispatch(getData())}>获取数据</button>
    13. </div>
    14. )
    15. }

    结果

    路由

    实现一个简单路由

    1. 先进行安装

    npm i react-router-dom

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

    1. 创建router文件夹,新建index.js,然后在index.js中写入路由规则
    1. import { createBrowserRouter } from 'react-router-dom'
    2. import Login from '../page/Login'
    3. import Article from '../page/Article'
    4. import Home from '../page/Home'
    5. // 使用的浏览器路由模式
    6. const routes = createBrowserRouter([
    7. {
    8. path: '/',
    9. element: <Home />,
    10. },
    11. {
    12. path: '/login',
    13. element: <Login />,
    14. },
    15. {
    16. path: '/article',
    17. element: <Article />,
    18. },
    19. ])
    20. export default routes
    1. 在index.js中,引入路由规则和路由注入方法,并进行配置
    1. import React from 'react'
    2. import ReactDOM from 'react-dom/client'
    3. import './index.css'
    4. // 引入路由规则数组
    5. import routes from './router'
    6. // 引入路由注入方法
    7. import { RouterProvider } from 'react-router-dom'
    8. // 引入数据仓库,只有文件夹,默认会引入文件夹下的index.js
    9. import store from './store'
    10. // 结构出提供者组件,它有一个stroe属性,属性值就是我们要传递的值
    11. import { Provider } from 'react-redux'
    12. const root = ReactDOM.createRoot(document.getElementById('root'))
    13. root.render(
    14. <React.StrictMode>
    15. <Provider store={store}>
    16. <RouterProvider router={routes}></RouterProvider>
    17. </Provider>
    18. </React.StrictMode>
    19. )
    1. 修改app.js(不修改也没事,直接删除也行,反正一进来就匹配的Home页面)
    1. export default function App() {
    2. return <>
    3. }

    路由跳转

    • 分为声明式导航和编程式导航
    声明式导航
    • 通过Link标签的to属性指定跳转的路由path
    1. import { useSelector, useDispatch } from 'react-redux'
    2. import { decrement, increment } from '../../store/modules/counterSlice'
    3. import { Link } from 'react-router-dom'
    4. export default function Home() {
    5. const count = useSelector((state) => state.counter.value)
    6. const dispatch = useDispatch()
    7. return (
    8. <div>
    9. <h2>Person -- {count}</h2>
    10. <button onClick={() => dispatch(increment())}>增加</button>
    11. <button onClick={() => dispatch(decrement())}>减少</button>
    12. <Link to="/login">跳转到登录页</Link>
    13. <Link to="/article">跳转到文章</Link>
    14. </div>
    15. )
    16. }
    编程式导航
    • 通过useNavigate这个方法来进行跳转
    1. import { useSelector, useDispatch } from 'react-redux'
    2. import { decrement, increment } from '../../store/modules/counterSlice'
    3. import { Link, useNavigate } from 'react-router-dom'
    4. export default function Home() {
    5. const count = useSelector((state) => state.counter.value)
    6. const dispatch = useDispatch()
    7. const navigate = useNavigate()
    8. const toLogin = () => {
    9. navigate('/login')
    10. }
    11. return (
    12. <div>
    13. <h2>Person -- {count}</h2>
    14. <button onClick={() => dispatch(increment())}>增加</button>
    15. <button onClick={() => dispatch(decrement())}>减少</button>
    16. <Link to="/login">跳转到登录页</Link>
    17. <button onClick={() => navigate('/article')}>跳转到文章页</button>
    18. <button onClick={toLogin}>跳转到登录页</button>
    19. {/* 下面这样写是不行的 */}
    20. {/* <button onClick={navigate('/login')}>跳转到登录页</button> */}
    21. </div>
    22. )
    23. }

    路由传参

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

    传递参数

    1. import { useSelector, useDispatch } from 'react-redux'
    2. import { decrement, increment } from '../../store/modules/counterSlice'
    3. import { Link, useNavigate } from 'react-router-dom'
    4. export default function Home() {
    5. const count = useSelector((state) => state.counter.value)
    6. const dispatch = useDispatch()
    7. const navigate = useNavigate()
    8. return (
    9. <div>
    10. <h2>Person -- {count}</h2>
    11. <button onClick={() => dispatch(increment())}>增加</button>
    12. <button onClick={() => dispatch(decrement())}>减少</button>
    13. <Link to="/login?name=zs&age=25">跳转到登录页</Link>
    14. <button onClick={() => navigate('/article?name=ls&age=26')}>
    15. 跳转到文章页
    16. </button>
    17. </div>
    18. )
    19. }

    结果:会显示在地址栏上

    接收参数

    1. import React from 'react'
    2. import { Outlet, useSearchParams } from 'react-router-dom'
    3. export default function Article() {
    4. const [params] = useSearchParams()
    5. const name = params.get('name')
    6. return (
    7. <div>
    8. 文章页面{name} <Outlet></Outlet>
    9. </div>
    10. )
    11. }
    /路径参数传参

    传递参数

    1. import { useSelector, useDispatch } from 'react-redux'
    2. import { decrement, increment } from '../../store/modules/counterSlice'
    3. import { Link, useNavigate } from 'react-router-dom'
    4. export default function Home() {
    5. const count = useSelector((state) => state.counter.value)
    6. const dispatch = useDispatch()
    7. const navigate = useNavigate()
    8. return (
    9. <div>
    10. <h2>Person -- {count}</h2>
    11. <button onClick={() => dispatch(increment())}>增加</button>
    12. <button onClick={() => dispatch(decrement())}>减少</button>
    13. <Link to="/login/100">跳转到登录页</Link>
    14. <button onClick={() => navigate('/article/100/1')}>跳转到文章页</button>
    15. </div>
    16. )
    17. }

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

    1. import { createBrowserRouter } from 'react-router-dom'
    2. import Login from '../page/Login'
    3. import Article from '../page/Article'
    4. import Home from '../page/Home'
    5. // 使用的浏览器路由模式
    6. const routes = createBrowserRouter([
    7. {
    8. path: '/',
    9. element: <Home />,
    10. },
    11. {
    12. path: '/login/:id',
    13. element: <Login />,
    14. },
    15. {
    16. path: '/article/:num/:id',
    17. element: <Article />,
    18. },
    19. ])
    20. export default routes

    结果:会显示在地址栏上

    接收参数

    Login:

    1. import React from 'react'
    2. import { useParams } from 'react-router-dom'
    3. export default function Login() {
    4. const params = useParams()
    5. const id = params.id
    6. return <div>登陆页面{id}div>
    7. }

    Article:

    1. import React from 'react'
    2. import { Outlet, useParams } from 'react-router-dom'
    3. export default function Article() {
    4. const params = useParams()
    5. const name = params.name
    6. const id = params.id
    7. return (
    8. <div>
    9. 文章页面{id}{name} <Outlet></Outlet>
    10. </div>
    11. )
    12. }

    嵌套路由

    配置嵌套路由

    1. import { createBrowserRouter } from 'react-router-dom'
    2. import Login from '../page/Login'
    3. import Article from '../page/Article'
    4. import Home from '../page/Home'
    5. // 使用的浏览器路由模式
    6. const routes = createBrowserRouter([
    7. {
    8. path: '/',
    9. element: <Home />,
    10. },
    11. {
    12. path: '/login/:id',
    13. element: <Login />,
    14. // 嵌套路由
    15. children: [
    16. {
    17. // 不需要带 /
    18. path: 'test',
    19. element: <Article />,
    20. },
    21. ],
    22. },
    23. {
    24. path: '/article/:id/:name',
    25. element: <Article />,
    26. },
    27. ])
    28. export default routes

    测试

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

    子路由内容就出来了

    设置初始默认页面

    去除path,然后添加index为true

    1. import { createBrowserRouter } from 'react-router-dom'
    2. import Login from '../page/Login'
    3. import Article from '../page/Article'
    4. import Home from '../page/Home'
    5. // 使用的浏览器路由模式
    6. const routes = createBrowserRouter([
    7. {
    8. path: '/',
    9. element: <Home />,
    10. },
    11. {
    12. path: '/login/:id',
    13. element: <Login />,
    14. // 嵌套路由
    15. children: [
    16. {
    17. // 不需要带 /
    18. // path: 'test',
    19. index: true,
    20. element: <Article />,
    21. },
    22. ],
    23. },
    24. {
    25. path: '/article/:id/:name',
    26. element: <Article />,
    27. },
    28. ])
    29. export default routes

    测试

    404页面

    • 一定要放到最后

    两种路由模式

    Hook补充

    useReducer

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

    案例

    1. import { useReducer } from 'react'
    2. import { Button } from 'antd'
    3. const Home = () => {
    4. const fn = (state, action) => {
    5. // 根据传入的不同类型,来进行对应的数据操作
    6. switch (action.type) {
    7. case 'INC':
    8. return state + 1
    9. case 'DEC':
    10. return state - 1
    11. default:
    12. return state
    13. }
    14. }
    15. // num是值,dispatchNum是修改状态的方法(参考useState)
    16. const [num, dispatchNum] = useReducer(fn, 0)
    17. const changeNum = (params) => {
    18. dispatchNum(params)
    19. }
    20. return (
    21. <div>
    22. <h3>{num}</h3>
    23. <Button onClick={() => changeNum({ type: 'INC' })}>num++</Button>
    24. <Button onClick={() => changeNum({ type: 'DEC' })}>num--</Button>
    25. </div>
    26. )
    27. }
    28. export default Home

    useMemo

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

    首先先看一个场景

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

    基本语法

    案例

    1. import React, { useState } from 'react'
    2. import { Button } from 'antd'
    3. export default function Home() {
    4. const [count1, setCount1] = useState(0)
    5. const [count2, setCount2] = useState(0)
    6. console.log('组件重新渲染了')
    7. const fn = () => {
    8. console.log('函数调用了' + count1)
    9. }
    10. return (
    11. <div>
    12. <h2>Home{fn()}</h2>
    13. <Button onClick={() => setCount1(count1 + 1)}>改变count1,count1++</Button>
    14. <Button onClick={() => setCount2(count2 + 1)}>改变count2,count2++</Button>
    15. </div>
    16. )
    17. }
    • fn函数只与count1有关,所以count1变化的时候触发fn函数是正确的。但是经过测试,会发现count2变化的时候也会触发fn函数,这是不对啊

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

    1. import React, { useMemo, useState } from 'react'
    2. import { Button } from 'antd'
    3. export default function Home() {
    4. const [count1, setCount1] = useState(0)
    5. const [count2, setCount2] = useState(0)
    6. console.log('组件重新渲染了')
    7. const fn = useMemo(() => {
    8. console.log('函数调用了' + count1)
    9. }, [count1])
    10. return (
    11. <div>
    12. {/* 由于缓存的是值,所以调用也需要改下 */}
    13. <h2>Home{fn}</h2>
    14. <Button onClick={() => setCount1(count1 + 1)}>改变count1,count1++</Button>
    15. <Button onClick={() => setCount2(count2 + 1)}>改变count2,count2++</Button>
    16. </div>
    17. )
    18. }

    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本身是一个泛型函数
    1. import { useState } from 'react'
    2. interface IObj {
    3. name: string
    4. age: number
    5. }
    6. function App() {
    7. const [obj] = useState<IObj>({
    8. name: 'zs',
    9. age: 25,
    10. })
    11. return (
    12. <>
    13. <h2>App</h2>
    14. {obj.name} -- {obj.age}
    15. </>
    16. )
    17. }
    18. export default App

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

    初始值为null
    • 有时候,state的初始值设置为了null。则类型就可以使用联合类型
    1. import { useState } from 'react'
    2. interface IObj {
    3. name: string
    4. age: number
    5. }
    6. function App() {
    7. const [obj] = useState<IObj | null>(null)
    8. return (
    9. <>
    10. <h2>App</h2>
    11. {obj?.name} -- {obj?.age}
    12. </>
    13. )
    14. }
    15. export default App

    props类型

    案例

    1. import { useState } from 'react'
    2. interface IProps {
    3. count: number
    4. }
    5. function App() {
    6. const [count] = useState(100)
    7. return (
    8. <>
    9. <h2>App</h2>
    10. <Son count={count}></Son>
    11. </>
    12. )
    13. }
    14. function Son(props: IProps) {
    15. return (
    16. <>
    17. <h3>子组件 {props.count}</h3>
    18. </>
    19. )
    20. }
    21. export default App
    为children添加类型

    为事件添加类型

    案例

    1. import { useState } from 'react'
    2. interface IProps {
    3. count: number
    4. children: React.ReactNode
    5. getMsg?: (str: string) => void
    6. }
    7. function App() {
    8. const [count] = useState(100)
    9. const getMsg = (msg: string) => {
    10. console.log('接收到了子组件的值:', msg)
    11. }
    12. return (
    13. <>
    14. <h2>App</h2>
    15. <Son count={count} getMsg={getMsg}>
    16. 传给子组件的内容
    17. </Son>
    18. </>
    19. )
    20. }
    21. function Son(props: IProps) {
    22. const { getMsg } = props
    23. const sendMsg = () => {
    24. getMsg?.('子向父传的值')
    25. }
    26. return (
    27. <>
    28. <h3>子组件 {props.count}</h3>
    29. <button onClick={sendMsg}>传给父组件的值</button>
    30. </>
    31. )
    32. }
    33. export default App

    useRef类型

    获取Dom使用

    案例

    1. import { useEffect, useRef } from 'react'
    2. function App() {
    3. const inputRef = useRef<HTMLInputElement>(null)
    4. useEffect(() => {
    5. inputRef.current?.focus()
    6. }, [])
    7. return (
    8. <>
    9. <h2>App</h2>
    10. <input ref={inputRef}></input>
    11. </>
    12. )
    13. }
    14. export default App
    当成稳定的存储器使用

    axios类型

    组合成了一种类型

    案例

    封装axios

    1. import axios from 'axios'
    2. const requestInstance = axios.create({
    3. baseURL: 'http://geek.itheima.net/v1_0',
    4. timeout: 5000,
    5. })
    6. requestInstance.interceptors.request.use(
    7. (config) => {
    8. return config
    9. },
    10. (error) => {
    11. return Promise.reject(error)
    12. }
    13. )
    14. requestInstance.interceptors.response.use(
    15. (response) => {
    16. return response
    17. },
    18. (error) => {
    19. return Promise.reject(error)
    20. }
    21. )
    22. export default requestInstance

    apis

    1. import { http } from '@/utils'
    2. // 定义通用泛型接口
    3. interface IResType<T> {
    4. data: T
    5. message: string
    6. }
    7. // 定义data类型
    8. interface IObj {
    9. id: number
    10. name: string
    11. }
    12. interface IDataType {
    13. channels: Array<IObj>
    14. }
    15. // 请求频道列表
    16. export const getChannels = () =>
    17. http.request<IResType<IDataType>>({
    18. url: '/channels',
    19. })

  • 相关阅读:
    vue 前端参值后端接收的几种方式
    【开题报告】基于SpringBoot的校园周边攻略平台的设计与实现
    Arduino PLC IDE
    前端工程化——前后端分离,进化流程
    2、Jvm类加载器和双亲委派机制
    《rust学习一》 fleet 配置rust环境
    简答一波 HashMap 常见八股面试题 —— 算法系列(2)
    使用DiskGenius拓展系统盘C盘的容量
    MindSpore和Python中nn.Unfold的区别
    【性能测试】JMeter:集合点,同步定时器的应用实例!
  • 原文地址:https://blog.csdn.net/qq_52845451/article/details/138209630