useState
声明 State 变量
import React, { useState } from 'react';
function Example() {
// 声明一个叫 "count" 的 state 变量
const [count, setcount] = useState(0);
声明变量的注意事项
- count和setcount一致(方便代码的后期维护)
- useState()参数你可以是数字,字符串,数组,对象,箭头函数
import React,{useState}from 'react'
export default function Test01() {
const [num, setCount] = useState(0);
const [name, setname] = useState("inno");
const [arr, setarr] = useState([0,1,2,3,4])
const [cou] = useState({name:"对象"})
const [count,setcount] = useState(()=>{
return 0
});
return (
<div>
{num}<br/>
{name}<br/>
{arr}
{cou}
{count}
</div>
)
}
读取变量
在函数中,我们可以直接用 count:
<p>You clicked {count} times</p>
更新state
<button onClick={() => setCount(count + 1)}> Click me
</button>
useState使用规则
- state设置重复的值—只会重复渲染一次
import React,{useState}from 'react'
export default function Test01() {
const [count,setcount] = useState(0);
console.log(count);
return (
<div>
{count}
<button onClick={()=>{
setcount(10)
}}></button>
</div>
)
}
代码分析
//1
count=0
渲染 //count=0
//2
count=10
渲染//count=10
//3
count=10
渲染//count=10
- useState,useEffect只在最顶层使用 Hook,不要在循环,条件或嵌套函数中调用 Hook
遵守这条规则,你就能确保 Hook 在每一次渲染中都按照同样的顺序被调用。这让 React 能够在多次的 useState 和 useEffect 调用之间保持 hook 状态的正确
React靠的是Hook的调用顺序,来确定那个state对应那个useState
// ------------
// 首次渲染
// ------------
useState('Mary') // 1. 使用 'Mary' 初始化变量名为 name 的 state
useEffect(persistForm) // 2. 添加 effect 以保存 form 操作
useState('Poppins') // 3. 使用 'Poppins' 初始化变量名为 surname 的 state
useEffect(updateTitle) // 4. 添加 effect 以更新标题
// -------------
// 二次渲染
// -------------
useState('Mary') // 1. 读取变量名为 name 的 state(参数被忽略)
useEffect(persistForm) // 2. 替换保存 form 的 effect
useState('Poppins') // 3. 读取变量名为 surname 的 state(参数被忽略)
useEffect(updateTitle) // 4. 替换更新标题的 effect
// ...
只要 Hook 的调用顺序在多次渲染之间保持一致,React 就能正确地将内部 state 和对应的 Hook 进行关联。但如果我们将一个 Hook (例如 persistForm effect) 调用放到一个条件语句中会发生什么呢?
// 🔴 在条件语句中使用 Hook 违反第一条规则
if (name !== '') {
useEffect(function persistForm() {
localStorage.setItem('formData', name);
});
}
在第一次渲染中 name !== ‘’ 这个条件值为 true,所以我们会执行这个 Hook。但是下一次渲染时我们可能清空了表单,表达式值变为 false。此时的渲染会跳过该 Hook,Hook 的调用顺序发生了改变:
useState('Mary') // 1. 读取变量名为 name 的 state(参数被忽略)
// useEffect(persistForm) // 🔴 此 Hook 被忽略!
useState('Poppins') // 🔴 2 (之前为 3)。读取变量名为 surname 的 state 失败
useEffect(updateTitle) // 🔴 3 (之前为 4)。替换更新标题的 effect 失败
React 不知道第二个 useState 的 Hook 应该返回什么。React 会以为在该组件中第二个 Hook 的调用像上次的渲染一样,对应的是 persistForm 的 effect,但并非如此。从这里开始,后面的 Hook 调用都被提前执行,导致 bug 的产生。
这就是为什么 Hook 需要在我们组件的最顶层调用。如果我们想要有条件地执行一个 effect,可以将判断放到 Hook 的内部:
useEffect(function persistForm() {
// 👍 将条件判断放置在 effect 中
if (name !== '') {
localStorage.setItem('formData', name);
}
});
useEffect
声明:useEffect(() => { effect return () => { cleanup }; }, [input]);
useEffect作用
- 推迟代码执行,渲染完成之后再执行 (获取ajax请求,异步获取数据)
import React,{useState, useEffect } from 'react'
export default function Test01() {
const [data,setdata] = useState(0)
useEffect(() =>
setdata(10),[data]
)
console.log("渲染第一次"+data);
return (
<div>
{data}
</div>
)
}
import React,{useState, useEffect } from 'react'
import Axios from 'axios';
export default function Test01() {
const [data,setdata] = useState(0)
useEffect(() => {
Axios('./a.json').then((res)=>{
setdata(res.data)
})
}, [setdata])
console.log("渲染第一次"+data);
return (
<div>
{data}
</div>
)
}
//第1次渲染
data==>0
渲染第一次:0
useEffect中的setdata触发重新渲染
//第2次渲染
data==>[inno,blue]
渲染第2次:[inno,blue]
useEffect中的setdata触发重新渲染
//第3次渲染
data==>[inno,blue]
渲染第3次:[inno,blue]
useEffect中的setdata触发重新渲染
两次state值一样,触发useState
的bailing out 机制。
- 完成移除 (定时器,监听)
import React, { useState ,useEffect} from 'react'
export default function Test01() {
const [count, setcount] = useState(0)
useEffect(() => {
let timer=setInterval(() => {
setcount(count+1)
}, 1000);
return () => {
clearInterval(timer)
}
})
return (
<div>
{count}
</div>
)
}
- 监听与跳过执行
import React, { useState,useEffect } from 'react'
export default function Test01() {
const [count, setcount] = useState(0)
const [num, setnum] = useState(0)
useEffect(() => {
let timer= setInterval(() => {
setnum(num+10)
}, 1000);
return () => {
clearInterval(timer)
}
}, [num])
useEffect(() => {
}, [count])
return (
<div>
{num}<br/>
{count}<br/>
<button onClick={()=>{setcount(count+1)}}></button>
</div>
)
}
- useEffect和useState盲目的结合使用时,常方法内存泄露
useRef
声明:const txt = useRef()
标记:<input ref={txt} defaultValue="asd"></input>
读取:txt.current.value
import React,{useRef,useEffect} from 'react'
export default function Test01() {
const txt = useRef()
useEffect(() => {
console.log(txt.current.value);
}
)
return (
<div>
<input ref={txt} defaultValue="asd"></input>
</div>
)
}
forwardref
传递引用
函数组件不像class组件一样,没有实例,因此父组件想要获取子组件的值,需要forwardref的帮助
import React ,{useRef, useEffect}from 'react'
import Nav from './nav'
export default function Test01() {
const txt = useRef()
useEffect(() => {
console.log(txt.current.value);
})
return (
<div>
<Nav ref={txt}/>
</div>
)
}
import React ,{forwardRef}from 'react'
export default forwardRef((props,ref)=> {
return (
<div>
<input ref={ref} defaultValue="sf"></input>
</div>
)
}
)
useContext
创建context对象:
const ThemeContext = React.createContext(defaultValue)
使用context对象
<ThemeContext.Provider value={themes.dark}>
<Toolbar />
</ThemeContext.Provider>
获取context对象的value
const theme = useContext(ThemeContext);
import React,{useContext} from 'react'
const themes = {
light: {
foreground: "#000000",
background: "#eeeeee"
},
dark: {
foreground: "#ffffff",
background: "#222222"
}
};
const ThemeContext = React.createContext(themes.light);
export default function Test01() {
return (
<ThemeContext.Provider value={themes.dark}>
<Toolbar />
</ThemeContext.Provider>
);
}
function Toolbar(props) {
return (
<div>
<ThemedButton />
</div>
);
}
function ThemedButton() {
const theme = useContext(ThemeContext);
console.log(theme);
return (
<button style={{ background: theme.background, color: theme.foreground }}>
I am styled by theme context!
</button>
);
}
useReducer
声明:
const [state, dispatch] = useReducer(reducer, initialState, init)
reducer:根据action.type返回state
(state, action) => newState
initialstate:初始化state
const initialState = {count: 0};
加数器案例
import React,{useReducer} from 'react'
const initialState = {count: 0};
function reducer(state, action) {
switch (action.type) {
case 'increment':
return {count: state.count + 1};
case 'decrement':
return {count: state.count - 1};
default:
throw new Error();
}
}
export default function Test01() {
const [state, dispatch] = useReducer(reducer, initialState);
return (
<>
Count: {state.count}
<button onClick={() => dispatch({type: 'decrement'})}>-</button>
<button onClick={() => dispatch({type: 'increment'})}>+</button>
</>
);
}
useMemo
声明:const memoizedValue = useMemo(() => function, [a, b]);
memoizedValue:usememo保存渲染的结果
() => function:函数作为参数
[a, b]:依赖项数组作为参数
原理:useMemo是解决react性能优化的问题,只有当依赖项数组发生改变时参数函数才会去执行
案例:点击a,child1发生渲染,b不会。点击b,child2发生渲染,child1不会。
import React, { useState, useMemo } from 'react'
const Child=({a})=>{
return <h2>{a}</h2>
}
function Parent({a,b}){
const child1=useMemo(() => {
console.log("这里a是child1渲染");
return <div>
<Child a={a}/>
</div>}, [a])
const child2=useMemo(() =>{
console.log("这里b是child2渲染")
return <div>
<Child a={b}/> </div>}, [b])
return <React.Fragment>{child1},{child2}</React.Fragment>
}
export default function Test01() {
const [a, seta] = useState(0)
const [b, setb] = useState(0)
return (
<div>
<Parent a={a} b={b}></Parent>
{a}<br/>
{b}
<button onClick={()=>{seta(a+1)}}>a</button>
<button onClick={()=>{setb(b+1)}}>b</button>
</div>
)
}
自定义HOOK
自定义的hook实质就是平时函数中我们提取封装函数,只是自定义的函数名必须是use开头。
另外由于hook本身就是函数,所以他们之间可以传递信息。
hook+redux
获取数据
const state = useSelector(state => state.state)
发送action
const dispatch = useDispatch(function)
**useSelector替代mapStateToProps
useDispatch替代mapDispatchToProps
同时将action写成方法
**
案例
子组件:
import React from "react";
import { useSelector, useDispatch } from "react-redux";
const Toggle = () => {
const go = useSelector(state => state.go);
const dispatch = useDispatch();
const asyncfunc = () =>{
setTimeout(()=>{
dispatch({ type: 'toggle' })
},1000)
}
return (
<div>
<div>{JSON.stringify(go)}</div>
<input
type="checkbox"
value={go}
onChange={() => asyncfunc()}
/>
</div>
);
};
export default Toggle;
store的创建文件 【无需再使用中间件,因为异步调用已解决 只需创建store即可】
//store.js
import {createStore} from 'redux'
import reducer from './reducer'
export default createStore(reducer)
在需要使用redux的最大组件进行 引入与包装 【不变】
import {Provider} from 'react-redux'
import store from './store' //此为store
import Toggle from './toggle'; //此为组件
render(
<Provider store={store}>
<Toggle/>
</Provider>,
document.getElementById('root')
);
总结:
去掉 - 子组件入口 【即书写中间件层】
增加 + 子组件的引入{ useSelector, useDispatch }
减少 - ./store.js创建时嵌套中间件