React基础汇总
基础
创建项目
借助脚手架,新建一个React项目
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语句
export default function App() {
const [num, setNum] = useState(100)
const [gender, setGender] = useState(0)
const print = () => {
console.log('123')
}
return (
<div>
{/* {}中可以填入变量 */}
成绩:{num}
{/* {}中可以填入三元 */}
性别:{gender === 0 ? '女' : '男'}
{/* {}中还可以写入方法 */}
方法:{print()}
</div>
)
}
列表渲染
- 通过
map
进行遍历,里面需要绑定key
值,方便diff
算法进行对比,提高diff
性能 - 重复渲染那个模板,就
return
谁 - key 在当前列表中要唯一的字符串或者数值(String/Number)
export default function App() {
const [list, setList] = useState([
{ id: 0, name: '张三' },
{ id: 1, name: '李四' },
{ id: 2, name: '王五' },
])
return (
<div>
{list.map((item) => (
<p key={item.id}>姓名:{item.name}</p>
))}
</div>
)
}
条件渲染
- 根据是否满足条件生成HTML结构,比如Loading效果
- 可以使用 三元运算符 或 逻辑与(&&)运算符 或 逻辑或(||)运算符
export default function App() {
const [flag, setFlag] = useState(true)
return (
<div>
{flag ? '正确的' : null}
{flag && '前面只有为true的情况下才会执行'}
{!flag || '前面只有为false的情况下才会执行'}
</div>
)
}
if系列判断渲染
- 可以声明一个方法,接收值,内部进行判断,并返回对应的结果
export default function App() {
const getType = (type) => {
if (type === 0) {
return <span>111</span>
} else if (type === 1) {
return <span>222</span>
} else if (type === 2) {
return <span>333</span>
}
}
return (
<div>
{getType(1)}
</div>
)
}
样式处理
-
利用
className
指定类名,适合样式比较多的情况 -
直接行内样式,适合样式比较少的
<h2 style={{ color: 'red' }}>标题</h2>
- 单独声明一个样式类名对象
export default function App() {
let style = {
color: 'pink',
fontSize: 20,
}
return (
<div>
<h2 style={style}>标题</h2>
</div>
)
}
动态类名
- 根据条件显示类名
export default function App() {
let style = {
color: 'pink',
fontSize: 20,
}
let [flag, setFlag] = useState(true)
return (
<div>
<h2 style={flag ? style : ''}>标题</h2>
</div>
)
}
注意事项
- JSX必须有一个根节点,如果没有根节点,可以使用<></>(幽灵节点)替代
- 所有标签必须形成闭合,成对闭合或者自闭合都可以
- JSX中的语法更加贴近JS语法,属性名采用驼峰命名法 class -> className for -> htmlFor
- JSX支持多行(换行),如果需要换行,需使用() 包裹,防止bug出现
小案例1
- 实现一个最基本的评论(不完整)
import './index.css'
import avatar from './images/avatar.png'
// 依赖的数据
const state = {
// hot: 热度排序 time: 时间排序
tabs: [
{
id: 1,
name: '热度',
type: 'hot',
},
{
id: 2,
name: '时间',
type: 'time',
},
],
active: 'hot',
list: [
{
id: 1,
author: '刘德华',
comment: '给我一杯忘情水',
time: new Date('2021-10-10 09:09:00'),
// 1: 点赞 0:无态度 -1:踩
attitude: 1,
},
{
id: 2,
author: '周杰伦',
comment: '哎哟,不错哦',
time: new Date('2021-10-11 09:09:00'),
// 1: 点赞 0:无态度 -1:踩
attitude: 0,
},
{
id: 3,
author: '五月天',
comment: '不打扰,是我的温柔',
time: new Date('2021-10-11 10:09:00'),
// 1: 点赞 0:无态度 -1:踩
attitude: -1,
},
],
}
// 时间格式化
const format = (time) => {
return `${time.getFullYear()}-${
time.getMonth() + 1 < 10 ? '0' + time.getMonth() + 1 : time.getMonth() + 1
}-${time.getDate() < 10 ? '0' + time.getDate() : time.getDate()} ${
time.getHours() < 10 ? '0' + time.getHours() : time.getHours()
}:${time.getMinutes() < 10 ? '0' + time.getMinutes() : time.getMinutes()}:${
time.getSeconds() < 10 ? '0' + time.getSeconds() : time.getSeconds()
}`
}
// tab切换
const activeClick = (active) => {
state.active = active
}
function App() {
return (
<div className="App">
<div className="comment-container">
{/* 评论数 */}
<div className="comment-head">
<span>5 评论</span>
</div>
{/* 排序 */}
<div className="tabs-order">
<ul className="sort-container">
{state.tabs.map((item) => {
return (
<li
className={state.active === item.type ? 'on' : ''}
onClick={() => activeClick('hot')}
key={item.id}
>
按{item.name}排序
</li>
)
})}
</ul>
</div>
{/* 添加评论 */}
<div className="comment-send">
<div className="user-face">
<img className="user-head" src={avatar} alt="" />
</div>
<div className="textarea-container">
<textarea
cols="80"
rows="5"
placeholder="发条友善的评论"
className="ipt-txt"
/>
<button className="comment-submit">发表评论</button>
</div>
<div className="comment-emoji">
<i className="face"></i>
<span className="text">表情</span>
</div>
</div>
{/* 评论列表 */}
<div className="comment-list">
{state.list.map((item) => {
return (
<div className="list-item" key={item.id}>
<div className="user-face">
<img className="user-head" src={avatar} alt="" />
</div>
<div className="comment">
<div className="user">{item.author}</div>
<p className="text">{item.comment}</p>
<div className="info">
<span className="time">{format(item.time)}</span>
<span
className={item.attitude === 1 ? 'like liked' : 'like'}
>
<i className="icon" />
</span>
<span
className={item.attitude === -1 ? 'hate hated' : 'hate'}
>
<i className="icon" />
</span>
<span className="reply btn-hover">删除</span>
</div>
</div>
</div>
)
})}
</div>
</div>
</div>
)
}
export default App
组件
- 分为函数式组件(rfc)和类组件(rnc)
- 安装
ES7+ React/Redux/React-Native snippets
这个插件后就可以使用上述指令快速创建组件 - 主要讲函数式组件
绑定事件
on
开头,后面紧跟事件名(事件名首字母大写)
on事件名 = { 事件处理函数名 } // 无参
on事件名 = { () => 事件处理函数名(参数1,参数2…) } // 有参
on事件名 = { (e) => 事件处理函数名(e,参数2…) } // 有参,带e的
- 事件处理函数
let/const 事件处理函数名 = (参数) => { … }
eg:
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详解
-
props是只读对象(readonly)
- 是自顶向下单向数据流,根据单项数据流的要求,子组件只能读取props中的数据,不能进行修改
-
props可以传递任意数据
- 数字、字符串、布尔值、数组、对象、函数(多用子向父传值)、JSX(类似于插槽)
父组件
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
中
使用步骤
- 首先创建一个独立的文件,
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属性
-
表示该组件的子节点,只要组件内部有子节点,props中就有该属性
-
children
属性,类似于插槽。直接写在标签中的内容会填充到children
属性上面 -
children
可以是普通文本,普通标签元素,函数 / 对象,JSX -
如果并列的传入多个,
props
的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>
)
}
props校验
- 下载 prop-types 插件 ,并导入prop-types包 yarn add prop-types
- 使用 组件名.propsTypes = { } 来给组件的props中的数据添加校验规则
- 校验规则通过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
进行结构。把里面的两项分别结构出来
格式:
let/const [ 数据 ,修改数据的方法 ] = useState(默认值)
eg:
import React from 'react'
import { useState } from 'react'
export default function App() {
const [count, setCount] = useState(0)
console.log(useState(0)) // (2) [0, ƒ]
const add = (num) => {
let newCount = count + num
setCount(newCount)
}
return (
<div>
<h2>App --- {count}</h2>
<button onClick={() => add(1)}>+1</button>
</div>
)
}
函数做为参数
useState
中也可以传入一个函数做为参数(初始值可能需要经过一些计算而得)- 参数只会在组件的初始渲染中起作用,后续渲染时会被忽略。如果初始 state 需要通过计算才能获得,则可以传入一个函数,在函数中计算并返回初始的 state,此函数只在初始渲染时被调用
格式:
const [name, setName] = useState(()=>{
// 编写计算逻辑 return ‘计算之后的初始值’
})
eg:
父组件
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发送
- 手动修改dom
- localstorage操作
执行时机
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 回调函数中用到的数据(比如,count)就是依赖数据,就应该出现在依赖项数组中,如果不添加依赖项就会有bug出现
清除副作用
- 在组件被销毁时,如果有些副作用操作需要被清除(比如定时器)
语法:
useEffect(() => {
// 副作用操作…
return () => {
// 写清除副作用的代码
}
})
eg: 清除定时器案例
父组件
import React from 'react'
import { useState } from 'react'
import Son from './pages/Son'
export default function App() {
const [flag, setFlag] = useState(true)
return (
<div>
<h2>App</h2>
<button onClick={() => setFlag(!flag)}>显示/隐藏组件</button>
{flag && <Son></Son>}
</div>
)
}
子组件
import React, { useEffect } from 'react'
export default function Son() {
// 组件进来的时候触发一个定时器
useEffect(() => {
let timer = setInterval(() => {
console.log('定时器执行了')
}, 1000)
// 组件销毁时清除定时器
return () => {
// 在return里面的函数里写清除操作
clearInterval(timer)
}
}, [])
return (
<div>
<h3>Son</h3>
</div>
)
}
useEffect
发送网络请求
- 依赖项要是一个空数组,因为依赖项为空数组时只会在页面初始化时触发一次
import React from 'react'
import { useEffect } from 'react'
export default function App() {
const getData = () => {
fetch('https://cnodejs.org/api/v1/topics')
.then((response) => response.json())
.then((data) => console.log(data.data))
}
useEffect(() => {
getData()
}, [])
return (
<div>
<h2>App</h2>
</div>
)
}
案例1
- 求卷去头部距离的hook
import { useState } from 'react'
export default function useWindowScroll() {
const [y, sety] = useState('')
window.addEventListener('scroll', function () {
sety(this.document.documentElement.scrollTop)
})
return [y]
}
使用
import React from 'react'
import useWindowScroll from './hook/useWindowScroll'
export default function App() {
const [y] = useWindowScroll()
return (
<div style={{ height: 1600 }}>
<h2>App -- {y}</h2>
</div>
)
}
案例2
- 数据改变,会同步到本地
import { useEffect, useState } from 'react'
export default function useLocalStorage(key, defaultVal) {
const [val, setVal] = useState(defaultVal)
// 只要val发生变化,就同步到本地
useEffect(() => {
localStorage.setItem(key, val)
}, [val, key])
return [val, setVal]
}
使用
import React from 'react'
import useWindowScroll from './hook/useWindowScroll'
import useLocalStorage from './hook/useLocalStorage'
export default function App() {
const [y] = useWindowScroll()
const [val, setVal] = useLocalStorage('val', 0)
const add = () => {
setVal(val + 1)
}
return (
<div style={{ height: 1600 }}>
<h2>
App -- {y} -- {val}
</h2>
<button onClick={add}>+1</button>
</div>
)
}
useRef
- 可以获取元素的真实Dom
import React, { useEffect, useRef } from 'react'
export default function App() {
const ipt = useRef(null)
useEffect(() => {
console.log(ipt.current.value)
}, [])
return (
<div>
<h2>App</h2>
<input type="text" ref={ipt} />
</div>
)
}
useContext
- 传输的数据是响应式的,跨组件传输数据用
- 如果传递的数据,只需要在整个应用初始化的时候传递一次就可以,则可以在
index.js
文件中提供数据 - 如果传递的数据需要状态维护,则可以在
app.js
中提供数据
使用步骤
- 创建一个
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
可以获取网页最左上的标题