前几日速成了一下react全家桶,在实战过程中,有点懵逼,来总结一下。
State
import React, { useState } from 'react';
function Example(){
const [ count, setCount ] = useState(0);
return(
<div onClick={()=>setCount(count + 1)}>Click Me!</div>
)
}
useState
会返回一对值:当前状态和一个让你更新的它的函数;setXXX
不会把新的state
和旧的state
进行合并,例如:state
可以是一个对象,在更新对象类型时,切记要合并旧的状态,否则旧的状态会丢失;
import React, { useState } from 'react';
function Example(){
const [ params, setParams ] = useState({
rotate: 0,
color: '#000'
});
const handleInputChange = event =>{
const traget = event.target;
setParams({
...params,
[target.name]:target.value
})
}
return()
}
// 声明多个 state 变量!
const [age, setAge] = useState(42);
const [fruit, setFruit] = useState('banana');
const [todos, setTodos] = useState([{ text: 'Learn Hooks' }]);
Effect
useEffect
就是一个 Effect Hook,给函数组件增加了操作副作用的能力。它跟 class 组件中的componentDidMount
、componentDidUpdate
和componentWillUnmount
具有相同的用途,只不过被合并成了一个 API。
import React, { useState, useEffect } from 'react';
function Example(){
const [ count, setCount ] = useState(0);
// 相当于 componentDidMount 和 componentDidUpdate.
// 默认情况下,React 会在每次渲染后调用副作用函数 —— 包括第一次渲染的时候。
useEffect(()=>{
document.title = `You Clicked ${count} times`;
})
return(
<div onClick={()=>setCount(count + 1)}>Click Me!</div>
)
}
useEffect
还可以在页面销毁时执行
useEffect(()=>{
// 相当于 componentDidMount 和 componentDidUpdate.
document.title = `You Clicked ${count} times`;
// 相当于 componentWillUnmount.
return ()=>{
console.log("我被销毁啦~");
}
})
useEffect
的第二个参数,当count
发生改变时,才会执行useEffect
里面的函数
useEffect(()=>{
// 相当于 componentDidMount 和 componentDidUpdate.
document.title = `You Clicked ${count} times`;
// 相当于 componentWillUnmount.
return ()=>{
console.log("我被销毁啦~");
}
},[count])
跟 useState
一样,你可以在组件中多次使用 useEffect
。
Context
这个hook应该是为了弥补props
。
import React, { useState, createContext, useContext } from 'react';
const CountContext = createContext();
function Test (){
const [ count, setCount ] = useState(0);
return(
<div>
<p>click: {count}</p>
<button onClick={()=>setCount(count+1)}>Clcik Me</button>
<CountContext.Provider value={count}>
<Counter/>
</CountContext.Provider>
</div>
)
}
function Counter(){
const count = useContext(CountContext);
return(
<h2>{count}</h2>
)
}
export default Test;
}
当不同文件的组件公用一个变量时,应该提取。
Reducer
之前有个例子是使用useState
写的计数器,每点击一次按钮,就加一
import React, { useState } from 'react';
function Test (){
const [ count, setCount ] = useState(0);
return(
<div>
<p>click: {count}</p>
<button onClick={()=>setCount(count+1)}>Clcik Me</button>
</div>
)
}
export default Test;
如果场景是加减呢,使用useState
就行不通,所以可以使用useReducer
。
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();
}
}
function Test (){
const [ state, dispatch ] = useReducer(reducer, initialState);
return(
<div>
<p>click: {state.count}</p>
<button onClick={()=>dispatch({type:'decrement'})}>decrement</button>
<button onClick={()=>dispatch({type:'increment'})}>increment</button>
</div>
)
}
export default Test;
Memo
useMemo
主要用来解决使用React hooks产生的无用渲染的性能问题。使用function的形式来声明组件,失去了shouldCompnentUpdate
(在组件更新之前)这个生命周期,也就是说我们没有办法通过组件更新前条件来决定组件是否更新。而且在函数组件中,也不再区分mount
和update
两个状态,这意味着函数组件的每一次调用都会执行内部的所有逻辑,就带来了非常大的性能损耗。
父组件中有a
和b
两个变量,两个AB按钮和一个子组件,点击A按钮改变a的值,点击B按钮改变b的值,子组件的内容会根据a的改变而改变。
import React, { useState } from 'react';
function Test (){
const [a, setA] = useState('我是A');
const [b, setB] = useState('我是B');
return(
<div>
<button onClick={()=>{setA('点击了A')}}>A</button>
<button onClick={()=>{setA('点击了B')}}>B</button>
<ChildComponent name={a}>我只子组件</ChildComponent>
</div>
)
}
function ChildComponent({name}){
function changeA(name){
console.log(`ChildComponent change ${name}`);
return `ChildComponent change ${name}`;
}
const actionA = changeA(name);
return (
<div>
<div>{actionA}</div>
</div>
)
}
export default Test;
虽然我们想要的是,当点击了A按钮,a的值发生了改变,然后子组件打印出ChildComponent change 点击了A
,但是发现点击B按钮也会打印,这不是我们希望的,可以使用useMemo
优化性能。
把“创建”函数和依赖项数组作为参数传入
useMemo
,它仅会在某个依赖项改变时才重新计算 memoized 值。这种优化有助于避免在每次渲染时都进行高开销的计算。
// 记得引入useMemo
const actionA = useMemo(()=>changeA(name),[name]);
我们可以用于导出的时候:
export default React.memo(Test)
补充PureComponent
和Component
class App extends PureComponent{}
class App extends Component{}
-
Component只要setState()和props的改变就会触发render
而我们不想页面重新render就得自己写shouldComponentUpdate来拦截render.shouldComponentUpdate(nextProps, nextState) { return nextProps.xxx.xx === props.xxx.xx; }
-
PureComponent为你处理shouldComponentUpdate事件
当props或state发生变化,PureComponent会对两者都做浅比较;浅比较只会检查基本数据类型的值是否相等(比如:1等于1或者true等于true),复杂如对象和数组只是比较引用值
并且只会去比较第一层的。
class App extends PureComponent {
state = {
items: [1, 2, 3]
}
handleClick = () => {
const { items } = this.state;
items.pop();
this.setState({ items });
}
render() {
return (<div>
<ul>
{this.state.items.map(i => <li key={i}>{i}</li>)}
</ul>
<button onClick={this.handleClick}>delete</button>
</div>)
}
}
会发现,无论怎么点 delete 按钮, li 都不会变少,因为 items 用的是一个引用。
handleClick = () => {
const { items } = this.state;
items.pop();
this.setState({ items: [].concat(items) });
}
useRef
useRef
返回一个可变的 ref 对象,其.current
属性被初始化为传入的参数(initialValue
)。返回的 ref 对象在组件的整个生命周期内保持不变。
const refContainer = useRef(initialValue);
function TextInputWithFocusButton() {
const inputEl = useRef(null);
const onButtonClick = () => {
// `current` 指向已挂载到 DOM 上的文本输入元素
inputEl.current.focus();
};
return (
<>
<input ref={inputEl} type="text" />
<button onClick={onButtonClick}>Focus the input</button>
</>
);
}