借助脚手架,新建一个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,根组件- 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谁- export default function App() {
- const [list, setList] = useState([
- { id: 0, name: '张三' },
- { id: 1, name: '李四' },
- { id: 2, name: '王五' },
- ])
- return (
- <div>
- {list.map((item) => (
- <p key={item.id}>姓名:{item.name}</p>
- ))}
- </div>
- )
- }
- export default function App() {
- const [flag, setFlag] = useState(true)
- return (
- <div>
- {flag ? '正确的' : null}
- {flag && '前面只有为true的情况下才会执行'}
- {!flag || '前面只有为false的情况下才会执行'}
- </div>
- )
- }
if系列判断渲染
- export default function App() {
- const getType = (type) => {
- if (type === 0) {
- return <span>111</span>
- } else if (type === 1) {
- return <span>222</span>
- } else if (type === 2) {
- return <span>333</span>
- }
- }
- return (
- <div>
- {getType(1)}
- </div>
- )
- }
className指定类名,适合样式比较多的情况<h2 style={{ color: 'red' }}>标题</h2>
- export default function App() {
- let style = {
- color: 'pink',
- fontSize: 20,
- }
- return (
- <div>
- <h2 style={style}>标题</h2>
- </div>
- )
- }
动态类名
- export default function App() {
- let style = {
- color: 'pink',
- fontSize: 20,
- }
- let [flag, setFlag] = useState(true)
- return (
- <div>
- <h2 style={flag ? style : ''}>标题</h2>
- </div>
- )
- }
动态类名插件

- import './index.css'
- import avatar from './images/avatar.png'
- // 依赖的数据
- const state = {
- // hot: 热度排序 time: 时间排序
- tabs: [
- {
- id: 1,
- name: '热度',
- type: 'hot',
- },
- {
- id: 2,
- name: '时间',
- type: 'time',
- },
- ],
- active: 'hot',
- list: [
- {
- id: 1,
- author: '刘德华',
- comment: '给我一杯忘情水',
- time: new Date('2021-10-10 09:09:00'),
- // 1: 点赞 0:无态度 -1:踩
- attitude: 1,
- },
- {
- id: 2,
- author: '周杰伦',
- comment: '哎哟,不错哦',
- time: new Date('2021-10-11 09:09:00'),
- // 1: 点赞 0:无态度 -1:踩
- attitude: 0,
- },
- {
- id: 3,
- author: '五月天',
- comment: '不打扰,是我的温柔',
- time: new Date('2021-10-11 10:09:00'),
- // 1: 点赞 0:无态度 -1:踩
- attitude: -1,
- },
- ],
- }
- // 时间格式化
- const format = (time) => {
- return `${time.getFullYear()}-${
- time.getMonth() + 1 < 10 ? '0' + time.getMonth() + 1 : time.getMonth() + 1
- }-${time.getDate() < 10 ? '0' + time.getDate() : time.getDate()} ${
- time.getHours() < 10 ? '0' + time.getHours() : time.getHours()
- }:${time.getMinutes() < 10 ? '0' + time.getMinutes() : time.getMinutes()}:${
- time.getSeconds() < 10 ? '0' + time.getSeconds() : time.getSeconds()
- }`
- }
- // tab切换
- const activeClick = (active) => {
- state.active = active
- }
-
- function App() {
- return (
- <div className="App">
- <div className="comment-container">
- {/* 评论数 */}
- <div className="comment-head">
- <span>5 评论</span>
- </div>
- {/* 排序 */}
- <div className="tabs-order">
- <ul className="sort-container">
- {state.tabs.map((item) => {
- return (
- <li
- className={state.active === item.type ? 'on' : ''}
- onClick={() => activeClick('hot')}
- key={item.id}
- >
- 按{item.name}排序
- </li>
- )
- })}
- </ul>
- </div>
-
- {/* 添加评论 */}
- <div className="comment-send">
- <div className="user-face">
- <img className="user-head" src={avatar} alt="" />
- </div>
- <div className="textarea-container">
- <textarea
- cols="80"
- rows="5"
- placeholder="发条友善的评论"
- className="ipt-txt"
- />
- <button className="comment-submit">发表评论</button>
- </div>
- <div className="comment-emoji">
- <i className="face"></i>
- <span className="text">表情</span>
- </div>
- </div>
-
- {/* 评论列表 */}
- <div className="comment-list">
- {state.list.map((item) => {
- return (
- <div className="list-item" key={item.id}>
- <div className="user-face">
- <img className="user-head" src={avatar} alt="" />
- </div>
- <div className="comment">
- <div className="user">{item.author}</div>
- <p className="text">{item.comment}</p>
- <div className="info">
- <span className="time">{format(item.time)}</span>
- <span
- className={item.attitude === 1 ? 'like liked' : 'like'}
- >
- <i className="icon" />
- </span>
- <span
- className={item.attitude === -1 ? 'hate hated' : 'hate'}
- >
- <i className="icon" />
- </span>
- <span className="reply btn-hover">删除</span>
- </div>
- </div>
- </div>
- )
- })}
- </div>
- </div>
- </div>
- )
- }
-
- export default App
ES7+ React/Redux/React-Native snippets这个插件后就可以使用上述指令快速创建组件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>
- )
- }
- 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>
- )
- }
- 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>
- )
- }
- 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>
- )
- }
- 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进行使用父组件
- 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>
- )
- }
父组件
- 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>
- )
- }
子组件
- 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
- 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>
- )
- }
index.js文件中提供数据,则全局都可以使用app.js中使用步骤
Context.js- import { createContext } from 'react'
-
- const Context = createContext()
-
- export default Context
Provider标签包裹根组件,value提供数据(提供的数据比较多,就可以使用对象形式)- import React, { useState } from 'react'
- import Son from './pages/Son'
- // 1. 引入Context
- import Context from './utils/context'
-
- export default function App() {
- const [msg, setMsg] = useState('根组件传递的数据')
-
- return (
- <>
- {/* 2. 使用Provider包裹上层组件提供数据 */}
- <Context.Provider value={msg}>
- {/* 根组件 */}
- <div>
- <h2>App</h2>
- <Son></Son>
- </div>
- </Context.Provider>
- </>
- )
- }
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属性,类似于插槽。直接写在标签中的内容会填充到children属性上面children可以是普通文本,普通标签元素,函数 / 对象,JSXprops的children属性会变成一个数组(就可以直接进行遍历)父组件
- 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>
- )
- }
检验基本语法
组件名.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,
- }
常见规则
- // 特定结构的对象
- obj: PropTypes.shape({
- name: PropTypes.string,
- age: PropTypes.number,
- }),
useState(初始值)返回值是一个数组(里面有两项)[数据,修改数据的方法] 是对 useState进行结构。把里面的两项分别结构出来格式:
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 中也可以传入一个函数做为参数(初始值可能需要经过一些计算而得)格式:
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函数的作用就是为react函数组件提供副作用处理的useEffect都是在组件dom渲染更新完毕之后才执行的副作用
副作用是相对于主作用来说的,一个函数除了主作用,其他的作用就是副作用。对于 React 组件来说,主作用就是根据数据(state/props)渲染 UI,除此之外都是副作用(比如,手动修改 DOM,ajax请求)
常见的副作用
执行时机
1.不添加依赖项
- useEffect(()=>{
- console.log('副作用执行了')
- })
- useEffect(()=>{
- console.log('副作用执行了')
- },[])
- 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(() => {
// 副作用操作...
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>
- )
- }
- import { useState } from 'react'
-
- export default function useWindowScroll() {
- const [y, sety] = useState('')
- window.addEventListener('scroll', function () {
- sety(this.document.documentElement.scrollTop)
- })
- return [y]
- }
-
- 使用
- import React from 'react'
- import useWindowScroll from './hook/useWindowScroll'
-
- export default function App() {
- const [y] = useWindowScroll()
- return (
- <div style={{ height: 1600 }}>
- <h2>App -- {y}h2>
- div>
- )
- }
- 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>
- )
- }
- 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>
- )
- }
index.js文件中提供数据app.js中提供数据使用步骤
context的文件createContext创建Context对象,并导出Provider提供数据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>
- )
- }

document.title 可以获取网页最左上的标题首先进行安装toolkit 和 react-redux
npm install @reduxjs/toolkit react-redux
在src目录下新建stroe文件夹,store文件夹下新建index.js和modules(模块)文件夹
src >> stroe >> index.js / mudules
store文件夹下的index.js里粘贴即可- import { configureStore } from '@reduxjs/toolkit'
-
- // 使用configureStore创建一个redux仓库
- // 并自动配置了 Redux DevTools 扩展 ,这样你就可以在开发时调试 store
- export default configureStore({
- // 此时我们还没有写入 reducer 后面这里再写入,有一个就写一个,也可以写入多个,在reducer大括号里写入
- reducer: {},
- })
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')
- )
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
下一步,我们需要从计数切片中引入 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,
- },
- })
现在我们可以使用 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>
- )
- }
结果

npm i react-router-dom

- import { createBrowserRouter } from 'react-router-dom'
- import Login from '../page/Login'
- import Article from '../page/Article'
- import Home from '../page/Home'
-
- // 使用的浏览器路由模式
- const routes = createBrowserRouter([
- {
- path: '/',
- element: <Home />,
- },
- {
- path: '/login',
- element: <Login />,
- },
- {
- path: '/article',
- element: <Article />,
- },
- ])
-
- export default routes
- 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>
- )
- export default function App() {
- return <>>
- }
- 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>
- )
- }
- 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
测试



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

首先先看一个场景

基本语法

案例
- 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>
- )
- }

就可以使用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>
- )
- }



测试结果


案例:

测试结果:



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

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

测试结果:



实现


案例

创建项目(使用的vite)
npm create vite@latest 项目名 -- --template react-ts


- 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里也可以传入一个函数,只要函数返回一个值就行
- 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

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


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

案例
- 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
- 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',
- })