一,useState
语法使用
const [参数,修改参数的方法] = useState(初始值)
// 变量 和 修改变量的方法
const [count, setCount] = React.useState(0)
// 定义对象
const [obj, setObj] = React.useState({ name: '张三', age: 18 })
// 定义数组
const [list, setList] = React.useState([])
举例一
import React from 'react'
export default function App() {
// 变量 和 修改变量的方法
const [count, setCount] = React.useState(0)
const add = () => {
setCount(count + 1)
}
return (
<div>
<p>{count}</p>
<button onClick={add}>+1</button>
</div>
)
}
点击按钮页面加一
举例二 修改对象的某一项值
写一个简单的页面 展示用户数据 添加按钮修改年龄为20岁
错误写法
import React from 'react'
export default function App() {
// 变量 和 修改变量的方法
const [user, setUser] = React.useState({ name: 'admin', age: 18 })
// 修改年龄
const editUser = () => {
setUser({ age: 20 })
}
return (
<div>
<div>姓名:{user.name}</div>
<div>年龄:{user.age}</div>
<button onClick={editUser}>修改年龄</button>
</div>
)
}
这时候我的eslint其实已经报错了
报错原因
提示了缺少了name属性 其实是因为userState是一个覆盖的操作 在调用setUser的时候需要传递一个对象对原有的对象进行替换
正确写法
可以通过结构赋值的方式进行修改
import React from 'react'
export default function App() {
// 变量 和 修改变量的方法
const [user, setUser] = React.useState({ name: 'admin', age: 18 })
// 修改年龄
const editUser = () => {
// 结构出对象中的每一项 然后之后声明的age就能覆盖结构的age
setUser({ ...user, age: 20 })
}
return (
<div>
<div>姓名:{user.name}</div>
<div>年龄:{user.age}</div>
<button onClick={editUser}>修改年龄</button>
</div>
)
}
这时候我们在点击修改就能成功修改了
举例三 如何修改数组的值
错误写法
import React from 'react'
export default function App() {
// 变量 和 修改变量的方法
const [user, setUser] = React.useState([
{ name: '小红', age: 18, id: 1 },
{ name: '小明', age: 20, id: 2 },
{ name: '小黄', age: 19, id: 3 }
])
// 新增小白
const add = () => {
user.push({ name: '小白', age: 18, id: 4 })
console.log(user)
setUser(user)
}
return (
<ul>
<button onClick={add}>新增一个小白</button>
{user.map(item => {
return (
<li key={item.id}>
<span>姓名:{item.name}</span>
<span>年龄:{item.age}</span>
</li>
)
})}
</ul>
)
}
错误原因
其实控制台已经打印过了表示的确是push进去了 但是页面并没有更新 是因为我们的数组地址没有发生变化页面监视不到 我们也需要通过传入一个新的数组进行替换
正确写法
import React from 'react'
export default function App() {
// 变量 和 修改变量的方法
const [user, setUser] = React.useState([
{ name: '小红', age: 18, id: 1 },
{ name: '小明', age: 20, id: 2 },
{ name: '小黄', age: 19, id: 3 }
])
// 新增小白
const add = () => {
setUser([...user, { name: '小白', age: 18, id: user.length + 1 }])
}
return (
<ul>
<button onClick={add}>新增一个小白</button>
{user.map(item => {
return (
<li key={item.id}>
<span>姓名:{item.name}</span>
<span>年龄:{item.age}</span>
</li>
)
})}
</ul>
)
}
新增成功
举例四 修改方法可以是一个函数
import React from 'react'
export default function App() {
const [count, setCount] = React.useState(0)
const add = () => {
// count是上一次的值
setCount(count => {
return count + 1
})
}
return (
<div>
<h1>{count}</h1>
<button onClick={add}>+1</button>
</div>
)
}
举例五 异步执行
import React from 'react'
export default function App() {
const [count, setCount] = React.useState(0)
const add = () => {
// 当我们连续调用setCount时,React会合并调用,只更新一次
setCount(count + 1)
setCount(count + 1)
setCount(count + 1)
}
return (
<div>
<h1>{count}</h1>
<button onClick={add}>+1</button>
</div>
)
}
并没有加3 是因为react进行判断更改 然后进行合并
通过函数的形式
import React from 'react'
export default function App() {
const [count, setCount] = React.useState(0)
const add = () => {
setCount(count => count + 1)
setCount(count => count + 1)
setCount(count => count + 1)
}
return (
<div>
<h1>{count}</h1>
<button onClick={add}>+1</button>
</div>
)
}
这时候我们点击
页面就变成了3
setTimeout异步包裹
import React from 'react'
export default function App() {
const [count, setCount] = React.useState(0)
const add = () => {
setTimeout(() => {
setCount(count => count + 1)
setCount(count => count + 1)
setCount(count => count + 1)
})
}
// 打印页面渲染次数
console.log('render')
return (
<div>
<h1>{count}</h1>
<button onClick={add}>+1</button>
</div>
)
}
在react18之之前我们在异步函数中修改几次 就会render几次 但是18之后 在异步函数中也会合并只render一次
怎么强制更新一次
import React from 'react'
import { flushSync } from 'react-dom'
export default function App() {
const [count, setCount] = React.useState(0)
const add = () => {
setTimeout(() => {
setCount(count => count + 1)
setCount(count => count + 1)
})
// 强行进行一次同步更新
flushSync(() => {
setCount(count => count + 1)
})
}
// 打印页面渲染次数
console.log('render')
return (
<div>
<h1>{count}</h1>
<button onClick={add}>+1</button>
</div>
)
}
这时候能发现render了两次
二,useEffect
useEffect用来模拟渲染后,更新后,销毁这三个动作
语法使用
import React from 'react'
export default function App() {
const [state, setState] = React.useState(0)
// useEffect(回调函数,依赖值)
// dom渲染完成执行
React.useEffect(() => {}, [])
// dom渲染完成执行一次 当state发生改变时候执行
React.useEffect(() => {}, [state])
// 内部return 函数 在组件销毁时候执行
React.useEffect(() => {
return () => {
console.log('销毁')
}
}, [state])
return <div>App</div>
}
举例一
当我们依赖项为数组的时候 初始化页面执行一次 可以用来获取我们的接口数据
React.useEffect(() => {
console.log('dom渲染完成')
axios.get('/api/user').then(res => {
setList(res.data)
})
}, [])
举例二
当我们第二个数组中有依赖值的情况下 当依赖值发生改变时候也会执行
import React from 'react'
export default function App() {
// 控制state始终是count的十倍
const [count, setcount] = React.useState(1)
const [state, setState] = React.useState(0)
// 监听count的变化,将count*10赋值给state
React.useEffect(() => {
setState(count * 10)
}, [count])
return (
<div>
<div>count:{count}</div>
<div>state:{state}</div>
<button onClick={() => setcount(count + 1)}>点击count++</button>
</div>
)
}
举例三
当我们在切换页面需要进行销毁操作的时候 比如清除定时器
import React from 'react'
export default function App() {
const [count, setcount] = React.useState(1)
const [state, setState] = React.useState(0)
React.useEffect(() => {
let T = setInterval(() => {
setState(count => count * 10)
}, 1000)
return () => {
clearInterval(T)
}
}, [count])
return (
<div>
<div>count:{count}</div>
<div>state:{state}</div>
<button onClick={() => setcount(count + 1)}>点击count++</button>
</div>
)
}
三,useMemo
语法
React.useMemo(() => {
return '必须有返回值'
}, ['依赖项'])
useMemo的使用跟vue的计算属性很相似 缓存值 当依赖项发生改变的时候会重新计算
例子
import React from 'react'
export default function App() {
const [count, setCount] = React.useState(1)
const [state, setState] = React.useState(5)
const total = () => {
// total依赖的是count不是state
console.log('total')
return count + 10
}
return (
<div>
<p>count:{count}</p>
<p>total:{total()}</p>
<p>state:{state}</p>
{/* 注意我们这里修改的是state,而不是count */}
<button onClick={() => setState(state + 1)}>state++</button>
</div>
)
}
上面的代码其实是定义了两个状态count state total是一个函数返回值为count的值加10也就是依赖count的值 因为在模板里调用了该函数 所以初始化会执行一次 当我们在点击修改state不是修改count 所以我们在修改state的时候 total这个函数不需要重新渲染 但是实际上当我们修改state 一样触发了total重新渲染
可以看到我们除了初始化调用了一次 我们点击按钮6次total触发了6次
实际上我们只需要当count发生改变时候重新计算total的返回值 其他是不需要重新执行的
这时候我们就可以使用useMemo包裹
import React from 'react'
export default function App() {
const [count, setCount] = React.useState(1)
const [state, setState] = React.useState(5)
const total = React.useMemo(() => {
console.log('total')
return count + 10
// 依赖count
}, [count])
return (
<div>
<p>count:{count}</p>
{/* 当使用usememo就是以值使用不需要加括号 */}
<p>total:{total}</p>
<p>state:{state}</p>
{/* 注意我们这里修改的是state,而不是count */}
<button onClick={() => setState(state + 1)}>state++</button>
</div>
)
}
这时候就变成了只有count改变才会重新计算
四,memo
memo是用来缓存组件的
例子
import React from 'react'
export default function App() {
// 定义一个状态
const [count, setCount] = React.useState(1)
console.log('父组件组件渲染')
return (
<div>
<div>count:{count}</div>
<button onClick={() => setCount(count + 1)}>count++</button>
<Child />
</div>
)
}
// 子组件
const Child = () => {
console.log('child子节点渲染')
return <div>我是子组件</div>
}
当我们修改父组件状态会重新render 子组件也会跟着一起render
我们只需要使用memo包裹就能解决
// 子组件
const Child = React.memo(() => {
console.log('child子节点渲染')
return <div>我是子组件</div>
})
五,useCallback
语法
React.useCallback(() => {
console.log('需要缓存的函数')
}, ['依赖的值'])
例子
import React from 'react'
export default function App() {
// 定义一个状态
const [count, setCount] = React.useState(1)
// 给子组件传递一个方法
const childClick = () => {
console.log('ChildClick')
}
console.log('父组件组件渲染')
return (
<div>
<div>count:{count}</div>
<button onClick={() => setCount(count + 1)}>count++</button>
<Child fn={childClick} />
</div>
)
}
// 子组件
const Child = React.memo(({ fn }: { fn: () => void }) => {
console.log('child子节点渲染')
return <div onClick={fn}>我是子组件</div>
})
主要是定义一个子组件并且使用了memo包裹 但是当我们点击父组件更新状态时候 依旧会重新创建子组件
原因是因为我们传递了一个引用类型数据一个点击函数 当父组件发生变化时 函数会重新创建 当地址发生变化时候 子组件就会重新渲染
所以我们需要使用useCallback来缓存一下传递的方法这样就能避免子组件render
import React from 'react'
export default function App() {
// 定义一个状态
const [count, setCount] = React.useState(1)
// 给子组件传递一个方法
const childClick = React.useCallback(() => {
console.log('ChildClick')
}, [])
console.log('父组件组件渲染')
return (
<div>
<div>count:{count}</div>
<button onClick={() => setCount(count + 1)}>count++</button>
<Child fn={childClick} />
</div>
)
}
// 子组件
const Child = React.memo(({ fn }: { fn: () => void }) => {
console.log('child子节点渲染')
return <div onClick={fn}>我是子组件</div>
})
这样我们在点击父组件状态 因为函数已经被缓存所以不会重新创建 子组件就不会渲染
所以useMemo是用来缓存值 useCallback是用来缓存函数 memo是用来缓存组件 需要相互配合使用节省性能
六,useRef
语法
跟vue中的ref差不多
const inputRef = React.useRef(null)
例子
import React from 'react'
export default function App() {
const inputRef = React.useRef(null)
const btnFn = () => {
console.log(inputRef.current)
}
return (
<div>
<input type='text' ref={inputRef} />
<button onClick={btnFn}>按钮</button>
</div>
)
}
点击按钮能拿到input结点
七, useTransition
语法
const [isPending, startTransition] = useTransition()
返回一个状态值表示过渡任务的等待状态,以及一个启动该过渡任务的函数。
例子
import React from 'react'
export default function App() {
const [isPendding, startTransition] = React.useTransition()
const [search, setSearch] = React.useState('')
const [list, setList] = React.useState<any>([])
const searchFn = (e: any) => {
setSearch(e.target.value)
// 如果不设置 startTransition 会跟search同步渲染
startTransition(() => {
let arr = Array.from({ length: 200 }).fill(1)
setList(arr)
})
}
return (
<div>
<input type='text' onChange={e => searchFn(e)} />
{isPendding ? (
<div>loading...</div>
) : (
list.map((item: any, index: any) => {
return <div key={index}>{search}</div>
})
)}
</div>
)
}
八,useReducer
语法
const [state, dispatch] = useReducer(reducer, initialArg, init);
nitialArg为初始数据
useReducer返回一个数组,包含state,dispath
action为判断事件类型,通过dispatch传递
例子
import { useReducer } from 'react'
const App = () => {
const [state, dispath] = useReducer(
(state: any, action: any) => {
// action{type,payload}
switch (action.type) {
case 'increment':
return {
count: state.count + 1
}
case 'decrement':
return {
count: state.count - 1
}
default:
return state
}
},
// 初始值
{
count: 0
}
)
return (
<div className='App'>
<div>{state.count}</div>
<button onClick={() => dispath({ type: 'increment' })}>increment</button>
<button onClick={() => dispath({ type: 'decrement' })}>decrement</button>
</div>
)
}
export default App
九,useContext
用于跨组件传值
例子
// 引入createContext,useContext
import { useState, createContext, useContext } from 'react'
// createContext创建分享数据
const Context: any = createContext(null)
// 爷爷组件
export default function App() {
const [n, setN] = useState(0)
return (
// 3.Context.Provider包裹需要传递参数的组件 value参数
<div>
<div>我是爷爷组件</div>
<Context.Provider value={{ n, setN }}>
<Child />
</Context.Provider>
</div>
)
}
// 子组件
function Child() {
// 通过useContext来获取数据
const { n } = useContext(Context) as any
return (
<div>
我是子组件 n: {n} <Child2 />
</div>
)
}
// 孙子组件
function Child2() {
// 通过useContext来获取数据
const { n, setN } = useContext(Context) as any
const onClick = () => {
setN((n: any) => n + 1)
}
return (
<div>
我是孙子组件:{n}
<button onClick={onClick}>修改爷爷数据</button>
</div>
)
}