useState
语法:const [n, setN] = React.useState(0);
0是n的默认值,setN是操作n的函数
setN: setN(i=>i+1)
- 我们以为setN会改变n,但是事实往往出乎意料,n并不会变,他是将改变后的数据存到一个数据里面(具体在哪里我们先定为x),而不是直接赋值去改变n
- setN一定会触发组件的重新渲染
useState:
- useState肯定会从x那里,读取到n的最新值
x:
- 每个组件都有自己的x,这个x我们命名为state
useRef
1.如果你需要一个值,在组件不断render的时候保持不变
// 在组件不断render的时候,count的值变了,但是页面显示不变
// 每次创建的是新的count,而不是用上一次的count
const count = useRef(0)
const onClick = () => {
count.current += 1
console.log(count)
}
// 每次页面也会发生变化,展示对应的值
const [_, set_] = useState(null)
const count = useRef(0)
const onClick = () => {
count.current += 1
set_(Math.random())
console.log(count)
}
// 使用useRef记录改变n多少次
const count = useRef(0)
useEffect(() => {
count.current += 1
console.log(count)
})
useContext
const themeContext = React.createContext(null);
function App() {
const [theme, setTheme] = React.useState("red");
return (
// themeContext.Provider 创建一个局部作用域
// 其中里面的所有组件都可以使用setTheme这个函数
{theme}
);
}
function ChildA() {
// ChildA这个子组件内部想使用父组件setTheme函数的方式
const { setTheme } = React.useContext(themeContext);
return (
);
}
useReducer
创建初始值
const store = {
user: null,
books: null,
movies: null,
}
创建所有操作的reducer
function reducer(state, action) {
switch (action.type) {
case 'setUser':
return { ...state, user: action.user }
case 'setBooks':
return { ...state, books: action.books }
case "setMovies":
return { ...state, movies: action.movies }
default:
throw new Error();
}
}
使用React提供的createContext
创建Context
// 使用React提供的createContext API,将子组件套住
const Context = createContext(null);
function App() {
return (
// 套住子组件
);
}
// 子组件User
function User() {
return (
个人信息
)
}
创建对数据读写的API
function App() {
// 使用useReducer对数据进行读写操作
const [state, dispatch] = useReducer(reducer, store)
return (
// 套住子组件 value是将读和写的API赋值给子组件
);
}
子组件中使用useContext
读取数据
function User() {
// 子组件中使用父组件数据的方式
const { state, dispatch } = useContext(Context)
// 使用useEffect,只在初次进入的时候才会发起请求
// 避免每次render的时候都会请求
// dispatch会改写初始化的数据
useEffect(() => {
ajax("/user").then(user => {
dispatch({ type: 'setUser', user })
});
}, []);
return (
个人信息
name: {state.user ? state.user.name : ""}
)
}
useEffect(render之后执行)
1.当useEffect,第二个参数为空数组时
// 第一个参数是执行函数
// 第二个参数如果是个空数组,只会在第一次render之后执行一次
// 后面再次render也不会执行
useEffect(() => {
console.log('第一次render之后执行!')
}, [])
2.当useEffect,第二个参数不传时
// 第一个参数是执行函数
// 第二个参数不传, 后面每次render都会执行
useEffect(() => {
console.log('每次render之后执行!')
})
2.当useEffect,第二个参数传需要变化的值时
// 第一个参数是执行函数
// 第二个参数传需要改变的值的时候(要放在数组里)
// 当n变化了,函数才会执行
useEffect(() => {
console.log('每次render之后执行!')
}, [n])
useLayoutEffect(DOM完毕之后,render之前执行)
使用的方式和useEffect一样,时间顺序不一样
memo
看下面这个图:
- 当我每次点击按钮的时候,Child函数组件都会执行
- 但是Child并没用用到n这个值
- 用memo封装一下,n的值在改变的时候,Child函数组件就不会执行了
- 只有m变化的时候才执行
- 有问题,当我们给子组件传递函数的时候,我们点击按钮
- Child函数组件还是执行了
useMemo
- 将传递给子组件的函数,放在
useMemo
中,第二个参数是要改变的值m - 这样就只有当m改变的时候,Child函数组件才会重新执行了
- 是不是觉得useMemo很别扭,因为他里面是函数作为参数,返回的还是一个函数,另一个API可以解决这样的问题:
useCallback
- useCallback和useMemo是一样的
const onClickChild = useCallback(() => {
console.log(m)
}, [m])
forwardRef
- 在函数式的组件中是无法使用ref获取子组件的
- 想要获取子组件使用forwardRef这个API
- 将子组件Button2用forwardRef包裹起来
- 这样就能获取到button的DOM
function App() {
const buttonRef = useRef(null)
return (
按钮
);
}
function Button2(props, ref) {
console.log(props)
console.log(ref)
return (
);
}
const Button3 = React.forwardRef(Button2)
自定义Hook
用一段代码来表示他的NB之处,下面这段代码useList是封装在了另一个文件里面
- 假设./hooks/useList这个文件里面是对list的增,删,改,查
import useList from "./hooks/useList"
function App() {
const { list, setList } = useList()
return (
List
{list ? (
{list.map(item => (
- {item.name}
))}
) : ("加载中.....")}
);
}
- 这个是./hooks/useList这个文件内部
- 这个文件只有一个假的ajax
- 还可以将增,改,删等操作封装进去,这就会将操作和展示分开
- 模块化会使代码格外清晰,这就是他的NB之处
import {
useState,
useEffect
} from "react"
const useList = () => {
const [list,
setList
] = useState(null)
useEffect(() => {
ajax("/list").then(list => {
setList(list)
})
}, [])
return {
list,
setList
}
}
export default useList;
function ajax(n) {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve([{
id: 1,
name: "Frank"
}, {
id: 2,
name: "Sun"
}, {
id: 3,
name: "Jack"
}])
}, 2000)
})
}