0.函数组件的基本结构
const Hello = (props) => {
return (<div>Hello{ props.message}</div>)
}
function Hello(props){
return (<div>Hello{ props.message}</div>)
}
两个核心问题
- 没有state
- 没有生命周期函数
解决方案: React Hooks
React Hooks
1. useState
参考函数式编程的思想,对环境的改变就是副作用
import React, { useEffect } from "react";
import { useState } from "react";
import ReactDOM from "react-dom"
function Hello(props){
return (<div>Hello{ props.message}</div>)
}
function App () {
const [count, setCount] = useState(0)
useEffect(() => {
console.log(`这是第${count}次点击`);
})
return (
<div>
<Hello message="123"/>
<p>Count:{count}</p>
<button onClick={()=>setCount(i=>i+1)}>+1</button>
</div>
)
}
ReactDOM.render(<App />, document.getElementById("root"))
useState这个函数接收的参数是状态的初始值(Initial state),它返回一个数组,这个数组的第0位是当前的状态值,第1位是可以改变状态值的方法函数。
对于setCount一定会修改数据count,将+1后的值存入,然后触发App 组件的重新渲染
useState则每次重新渲染时执行,读取count的最新值
多状态声明
- 每一个函数组件对应一个React节点
- state使用类似于数组的方案进行存储
- 每个节点保存自己的state和index-
- 使用useState是读取
state[index] - index根据useState出现的顺序来确定的
- setState修改对应的State,触发重新渲染
- useState不能出现在条件语句内
布局副作用 uselayoutEffect
useEffect 在浏览器渲染完成后执行
uselayoutEffect在浏览器渲染前执行,早于useEffect
function App () {
const Re = useRef(null)
useEffect(() => {
TweenMax.to(Re.current, 0, { x: 600 })
},[])
return (
<div className="out">
<div ref={Re} className="square">square</div>
</div>
);
}
body{
background-color: yellow;
}
.out{
color: #66FFFF;
width: 100px;
height: 100px;
}
.square{
background-color: orange;
height: 1000px;
}
会发现中间的方块一闪而过
模拟生命周期函数
基本原则
- React首次渲染和之后的每次渲染都会调用一遍
useEffect函数。 - useEffect中定义的函数的执行不会阻碍浏览器更新视图,也就是说这些函数时异步执行的,而
componentDidMonut和componentDidUpdate中的代码都是同步执行的。
模拟方案
- 模拟
componentDidMount
useEffect(() => {
console.log("初始化");
},[])
- 模拟
componentDidUpdate
useEffect(() => {
if(count>0) console.log(`这是第${count}次点击`);
},[count])
- 模拟
componentWillUnmount
useEffect(() => {
console.log("进入组件");
return () => {
console.log("推出组件");
}
},[])
传入空数组时代表组件销毁再解绑
2 useReducer
什么是Reducer
reducer其实就是一个函数,这个函数接收两个参数,一个是状态,一个用来控制业务逻辑的判断参数。
function countReducer(state, action) {
switch(action.type) {
case 'add':
return state + 1;
case 'sub':
return state - 1;
default:
return state;
}
}
使用方法
- 创建初始值
- 编写Reducer,创建所有操作
- 传给useReducer,得到所有读写操作
- ({"type":操作})调用
const initalCount = {
count:0
}
const reducer = (state, action) => {
switch (action.type) {
case 'add':
return { count: state.count + 1 };
case 'sub':
return { count: state.count - 1 };
default:
return {count:state.count};
}
}
function App () {
const [state, dispatch] = useReducer(reducer,initalCount)
const { count } = state
return (
<div>
<h2>现在的分数是{count}</h2>
<button onClick={() => dispatch({ type: 'add'})}>Increment</button>
<button onClick={() => dispatch({ type: 'sub'})}>Decrement</button>
</div>
)
}
模拟Redux
- 将数据集中于一个store对象
- 将所有操作集中于一个reducer中
- 创建一个Context
- 创建对数据读写的API
- 将输入读写的API放入第三步创建的Context中
- 用Context.Provider 将Context 提供给所有组件
- 组件使用useContext获取数据读写API
3. useContext
- 使用
C=createContext(inital)创建上下文 - 使用
<C.provider>规定作用域 - 在对应的作用域中使用
useContext(C)使用上下文
useContext 不是响应式
4.useMemo
主要用来解决使用React hooks产生的无用渲染的性能问题,防止函数组件的每一次调用都会执行内部的所有逻辑。
function App () {
const [n, setN] = useState(0)
const [m, setM] = useState(0)
const onClick = () => {
setN(n + 1);
};
const onClick2 = () => {
setM(m + 1);
};
const onClickChild = useMemo(
() => {
const fn = div => {
console.log("on click child, m: " + m);
console.log(div);
};
return fn;
}, [m]// 如果是函数返回函数可以用useCallback代替
)
return (
<div className="App">
<div>
<button onClick={onClick}>update n {n}</button>
<button onClick={onClick2}>update m {m}</button>
</div>
<Child2 data={m} onClick={onClickChild} />
</div>
)
}
function Child (props) {
console.log("child 执行了");
console.log("假设这里有大量代码");
return <div onClick={e => props.onClick(e.target)}>child: {props.data}</div>;
}
const Child2 = React.memo(Child);
- 第一个参数是 ()=> value,回调执行逻辑
- 第二个参数是更新的依赖
- 只有依赖变化时才重新计算value,执行对应的回调逻辑,参考Vue的计算属性
useCallback
useCallback(x=>log(x),m)
等价于
useMemo(()=> x =>log(x),m)
5.useRef
用于在重新render的过程中保持不变的值
import React, { useRef} from 'react';
function Example8(){
const inputEl = useRef(null)
const onButtonClick=()=>{
inputEl.current.value="Hello"
console.log(inputEl) //输出获取到的DOM节点
}
return (
<>
{/*保存input的ref到inputEl */}
<input ref={inputEl} type="text"/>
<button onClick = {onButtonClick}>在input上展示文字</button>
</>
)
}
export default Example8
使用:const count = useRef(0)
读取:count.current,通过引用保证两次的ref是同一个值
使用时不会自动更新UI,这一点Vue不同
forwardRef
实现Ref在props中的传递
import React, { useRef } from "react";
import ReactDOM from "react-dom";
import "./styles.css";
function App() {
const buttonRef = useRef(null);
return (
<div className="App">
<Button3 ref={buttonRef}>按钮</Button3>
</div>
);
}
const Button3 = React.forwardRef((props, ref) => {
return <button className="red" ref={ref} {...props} />;
});
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
useImperativeHandle
自定义Ref 用于包装属性