Hooks是在版本16.8新增的特性;
Hooks到底是为何而存在,从React官方文档的描述中,貌似跟class组件的复杂性和分明的生命周期有一定的关系,当然官方也说了不会移除class组件的,class组件目前来说还是React的核心之一,Hooks具有兼容性即随时随地可以使用,目前只在函数组件中使用,可以说是函数组件的福音,使函数组件也能在一定程度上使用class组件的特性如state、refs、context等;先看Hooks都做了哪些事情;
1,基础Hooks:useSate、useEffect、useContext: ;
useState: useState的使用: var [count, setCount] = useState(?defaultVal); useState返回了一个数组,该数组的第一元素是state、第二个元素是对应的用来更新state的函数;可以给useState传入一个默认值,该默认值可以是数值如字符串'string' 或者一个返回初始state的函数,在组件第一次渲染时会使用该值,之后的重新渲染会忽略该值(即如果传入的是个函数也不会被执行)而是用传给setCount(newValue)的新值,因此count拿到的是个上一次set的新值;更新state的变量是总是替换它而不像class组件的是合并,可以使用函数式的setState结合展开运算符达到这种效果如setCount( (state) => [...state, ...newStates]);setCount的第一个参数除了是数值外还可以是函数(state)=>{ return newVal},setCount( state => state+1);这里的state是React内部传过来的对应着'count'的当前最新值,这在某些情境下很有用,如在一个useEffect中不想受重渲染的影响生成新effect而使用空依赖列表且同时使用state'count'的时候;
function FnComp(){
var [count, setCount] = React.useState(0);
function changeCount(){
setCount(count + 1);
//setCount( state => state+1;);
}
return <div onClick={setCount}>{state}</div>
}
function FnComp1(){
var [count, setCount] = React.useState(0);
useEffect(()=>{
const id = setInterval(()=>{
setCount(c => c + 1);//不用这样形式:setCount(count + 1);
//因为当使用空列表时,可以认为依赖项不管何时都没有变化,
//useEffect也就没有替换上一次的effect,因此count的值还是初始值;
}, 1000);
return ()=> clearInterval(id);
}, []); //
return <div>count</div>
}
useEffect: useEffect的使用:useEffect(create, ?deps);第一个参数一定是个函数,可以认为它是个effect,在该函数中执行一些副作用的操作,比如操作DOM元素、异步获取数据、订阅等,该函数可选返回一个函数作为函数组件重渲染前清除副作用如取消订阅、移除事件监听器等;第二个参数是依赖列表,它是个数组,每个元素有可能是state或者props,React会比较该数组在两次渲染的值,若任何一个元素改变了,useEffect都会用新的effect替换之前的effect并执行上一次的取消effect的函数;比较规则使用Object.is,这表示若元素中有对象且对象的指向不一致则表示依赖项改变了;组件第一次渲染时不需要做比较;useEffect的功能相当于class组件的componentDidMount、componentDidUpdate、componentDidWillUnmount的组合;useEffect在每次渲染都是生成一个新的Effect,替换之前的Effect(在没有依赖列表的比较的情况下);React保证运行effect的同时,DOM都已经更新完毕(即effect会在浏览器完成布局和绘制之后,在下一个JS事件循环setTimeout中被调用),因此在此可以操作DOM;
function SomeComp({productName}){
let [count, setCount] = useState(0);
useEffect(()=>{
document.title = productName;
}, [productName]); //当productName变化时,才会替换effect、执行新的effect;
useEffect(()=>{
let id = setInterval(()=>{
setCount( c=> c+1);
}, 1000);
return ()=> clearInterval(id);
}, []);//一个计时器可以不需要每次重渲染时都创建一个,
//另外是每次渲染都clearInterval,可能会导致计时不太准;
useEffct(()=>{
console.log('component has mounted/updated');
});//有一些effect不需要依赖项;
return <div>count</div>
}
useContext: useContext可以让函数组件不需使用Context.Consumer,而是var contextValue = useContext(ThemeContext)也可以使用离该组件最近的该Context的值;ThemeContext是由React.createContext(defaultValue)创建的Context对象;类class组件利用静态属性ClassComp.contextType=ThemeContext,然后在组件调用this.context来使用ThemeContext的value;当离使用useContext的组件最近的该Context.Provider的值更新了,该hook会触发该组件更新,不管组件是使用React.memo(var comp = React.memo( function(props){return ...;}))优化过的或祖先组件使用shouldComponentUpdate(class parentComp exetends{ shouldComponentUpdate(nextProps, nextState){ return false;}} 但是按以下例子尝试,shouldComponentUpdate忽略渲染时,使用了useContext的子组件并没有因context的值的改变而重渲染*****),使用了useContext的组件都会重新渲染;
//如下当祖先组件shouldComponentUpdate返回false,取消重渲染时,
//其实使用useContext的组件并没有因为Context的值得改变而重渲染;
let ColorContextn = React.createContext('Blue');
var fnComp = function (){
console.log('fnComp');
let [state, setState] = useState('hello');
var color = useContext(ColorContext);
return <div style={{backgroundColor: 'pink'}}>
'Hellooooo'
{color}
</div>;
};
class App extends React.Component{
constructor(props){
super(props);
this.state={
color: 'Red'
};
this.click = this.click.bind(this);
this.button = React.createElement('button', {onClick: this.click}, ['App']);
}
shouldComponentUpdate(nextProps, nextState){
return false;
}
click(){
this.setState({color: this.state.color + 1});
}
render(){
return <ColorContext.Provider value={this.state.color}>
<div>
<fnComp></fnComp>
<button onClick={this.click}>App</button>
</div>
</ColorContext.Provider>;
}
}
//若组件是使用React.memo优化得,当Context得值改变了,使用了useContext的组件会重渲染;
//就是把App组件的shouldComponentUpdate去掉;
var fnComp = React.memo(function (){
console.log('fnComp');
let [state, setState] = useState('hello');
var color = useContext(ColorContext);
return <div style={{backgroundColor: 'pink'}}>
'Hellooooo'
{color}
</div>;
});
2,额外的Hooks:useReducer、useRef、useCallback、useMemo、useLayoutEffect
useReducer:相对比较复杂些,但理解便好;useReducer是个函数,返回一个数组,这个数组的第一个元素是state、第二元素是dispatch(这是个函数);var [state, dispatch] = useReducer(reducer, ?intialState, ?init); useReducer的第一个参数是个函数reducer,它接收当前的state以及用户数据,进行需要的逻辑操作,然后reducer返回state的新值,而这个reducer的调用被放在dispatch函数里执行;reducer可以与组件分离单独放在一个纯函数中;如
function reducer(state, action){
//do something;
//action is payload;
return newStateVal;
}
function SomeComp(){
var [state, dispatch] = useReducer(reducer, initialState);
function handlerSomeClick(){
let payload = {};
dispatch(payload);
}
}
第二个参数是初始state,第三个参数是用来处理初始state的函数,在在第一次渲染期间会执行,其他重新渲染期间会被忽略,包括intialState;当state的逻辑比较复杂且包含多个子值,或者下一个state依赖之前的state,且可以给子组件传递dispatch而不是回调函数(useReducer也可以优化性能,因为dispatch在组件的整个生命周期内没有被替换过,是初始时的那个dispatch),那么使用useReducer而不是useState;
//如下例子,useReducer同样是用的state替换之前的state而不是合并,
//所以要想达到合并的效果,使用扩展运算符;
function reducer(preState, action){
//do something;
//在useReducer,reducer拿到的action是dispatch函数接收的数据,
//且没有经过包装;
//return action;
return {..preState, ...action};
}
let ha = null;
var fnComp = function (){
let [state, dispatch] = React.useReducer(reducer, {color: 'red', count: 2});
console.log('fnComp');
ha = dispatch;
function click(){
let payload = {count: 5};
dispatch(payload);
}
return <div onClick={click} style={{backgroundColor: 'pink'}}>
Hellooooo,color
</div>;
};
useRef:像React.createRef函数返回一个Ref,var ref = useRef(initialCurrentValue);返回的ref是个对象,在组件的整个生命周期内持续存在,ref.current的初始值为 initialCurrentValue,对ref的使用就像React.createRef创建的Ref那样使用;useRef的用处比较广且比较随意,因为创建的Ref对象不仅仅用于引用一个对象,它可以用于存储一个普通的数据,且Ref对象的内容发生改变时,useRef不会发出通知,另外引用的是DOM对象时,如果想在React绑定或解绑DOM节点时执行一些代码,可以使用回调ref;*注:ref.current的值可变;
function SomeComp(){
var ref = useRef(null);
return <input ref={ref} />; // ref={ someRef => ref=someRef;}
}
//回调ref
function Comp(){
var ref = useRef(null);
return <input ref={ elemnt=> { ref.current = element;//do something}} />;
}
useCallback 和 useMemo利用第二个参数依赖项可以保留原来的函数以免因组件的重渲染而新建新的函数;把回调函数传给经过优化的并使用相等性避免不必要的渲染(例如shoudComponentUpdate)的子组件时会挺有用;
useCallback:它的调用形式是var fn = useCallback(fn, ?depsArray);
class childComp extends React.Component{
constructor(props){
super(props);
this.click = this.click.bind(this);
}
shouldComponentUpdate(nextProps, nextState){//前后的props相等;
return !Object.is(this.props.test, nextProps.test);
}
click(){
this.props.test();
}
render(){
return <div>
<input />
<button onclick={this.click}>click</button>
<div>
}
}
function fnComp(props){
let [state, setState] = useState('hello');
let callback = useCallback((data)=>{
setSate(data);
}, []);//因为不需要新建callback;
return <childComp test={callback}/>;
}
//那么当fnComp因某些原因重渲染时,传给子组件的'callback'并没有改变,
//因此,在使用shouldComponentUpdate的子组件中,
//可以通过判断props的变化决定自身的DOM需不要更新;props没有改变,子组件可以避免非必要的更新;
useMemo:它的调用形式是var fn = useMemo( ()=> fn, ?depsArray); useCallback(fn, ?deps) 相当于useMemo(()=>fn, ?deps);它们的区别是传给useMemo的函数会在渲染期间执行并返回fn;且不一定返回函数,可以是其他经过复杂计算的数据且希望可以在某些条件下存储起来不用频繁计算;
useLayoutEffect: 其函数签名与useEffect一样,只是useLayoutEffect相当于class组件的componentDidMount和componentDidUpdate的两阶段,useLayoutEffect的effect会在所有DOM更新后同步调用effect(useLayoutEffect的effect在执行时,能拿到最新的DOM数据,因此可以读取DOM的布局并同步触发重渲染),在浏览器执行绘制前,useLayoutEffect内的所有更新计划将同步更新,而不是在浏览器完成渲染和绘制之后再延迟执行;
var fnComp = function (){
let [top, setTop] = React.useState(10);
let ref = React.useRef(null);
React.useLayoutEffect(()=>{
console.log(ref);
console.log(ref.current);
console.log(ref.current.style.top);//top的数值是state的值
ref.current.style.top = 0;
});
function click(){
setTop( top=> top+20);
}
return <'input' onClick={click} style={{backgroundColor: 'pink', position: 'absolute', top: top+'px'}} ref=ref />;
};
useDebugValue:
useImperativeHandle:
***注:保证Hooks的正常运行,就要保证Hooks的调用顺序,这也是Hooks的使用规则的体现;就像是React在处理Hooks时,有这样的一种机制:每个函数组件有一个独立的指针存储列表,第一个位置存的是第一个Hook调用的结果,依次类推;因此,比如调用的是useState,useState返回的是一个数据以及一个回调函数,当调用该回调函数时,它就知道要更新哪个位置上的数据,在组件重新渲染时,React就知道这个指针存储列表,并按Hooks调用的顺序返回对应位置的数据以及回调函数;因此也可以说指针存储列表在该组件中是否是可复用的;那组件是完全销毁而不是更新的时候,指针存储列表是怎么知道是要销毁还是更新,估计是父级组件的判断?在父级组件需要更新时就知道哪个子组件是更新还是销毁,如果是自身组件的更新那基本上就是更新而不是销毁?;
函数组件重渲染的情况:父组件重渲染了,某些hook触发的重渲染,自身组件中的state发生了改变;
其他文档:
参考文档: