react hooks进阶与原理
上一篇文章介绍了react hooks提出的原因、主要的useState,useContext,useEffect,以及如何开发自定义的hooks,接下来将进一步扩展。
useEffect/useLayoutEffect
- useEffect: 我们可以看到上面上一篇文章里面对useEffect的介绍:useEffect用于处理大多数副作用,其中的回调函数会在render执行之后在调用,确保不会阻止浏览器的渲染,这跟componentDidMount和componentDidUpdate是不一样的,他们会在渲染之后同步执行。
- useLayoutEffect: 在大多数情况下,我们都可以使用useEffect处理副作用,但是,如果副作用是跟DOM相关的,就需要使用useLayoutEffect。useLayoutEffect中的副作用会在DOM更新之后同步执行。
举个简单的useLayoutEffect的例子:
function App() {
const [width, setWidth] = useState(0);
useLayoutEffect(() => {
const title = document.querySelector('#title');
const titleWidth = title.getBoundingClientRect().width;
if (width !== titleWidth) {
setWidth(titleWidth);
}
});
return <div>
<h1 id="title">hello</h1>
<h2>{width}</h2>
</div>
}
useReducer
useReducer非常有用,能够替代一部分redux的功能。举个官方的例子吧:
const reducer = (state, action) => {
switch(action.type) {
case 'increment':
return {count: state.count + 1};
case 'decrement':
return {count: state.count - 1};
case 'reset':
return initState;
default:
return state;
}
};
function App() {
const [state, dispatch] = useReducer(reducer, initState);
return <div>
<h1>{state.count}</h1>
<button onClick={() => dispatch({type: 'increment'})}>+</button>
<button onClick={() => dispatch({type: 'decrement'})}>-</button>
<button onClick={() => dispatch({type: 'reset'})}>reset</button>
</div>
}
useReducer接收两个参数,一个是reducer函数,跟redux中的reducer是一样的;另外一个是初始的状态值。返回的是一个数组,数组中的第一个元素是状态值,第二个元素是dispatch函数,你可以调用dispatch函数,来触发state的更新。
useRef
使用class组件,其中一个很重要的点事有时候我们需要获取某个组件或DOM节点的引用,借助useRef,我们可以在函数组件中获取组件或DOM节点的引用:
function App() {
const inputRef = useRef(null);
return <div>
<input type="text" ref={inputRef}/>
<button onClick={() => inputRef.current.focus()}>focus</button>
</div>
}
上面这段代码,点击button出触发input的focus事件,非常简单。
React Hooks简单原理:只是数组
最开始接触到React Hooks的时候,觉得它十分的优秀,但又很神秘,最近看到一篇文章,简单的介绍了React Hooks的原理,逐渐解开了React Hooks神秘的面纱。
Hooks的使用有两个原则:
- 不要在循环,条件判断,函数嵌套中使用hooks
- 只能在函数组件中使用hooks
第二个原则很好理解,hooks的提出主要是为了解决class组件的一系列问题;但是第一个原则却让人感到困惑。
看一个使用useState的例子:
function RenderFunctionComponent() {
const [firstName, setFirstName] = useState("Rudi");
const [lastName, setLastName] = useState("Yardley");
return (
<Button onClick={() => setFirstName("Fred")}>Fred</Button>
);
}
1、初始化
初始化的时候,会创建两个数组: state和setters,把光标的位置设为0.
2、第一次渲染
调用useState时,第一次渲染,会将一个set函数放入setters数组中,并且把初始state放入到state数组中.
3、后续渲染
每一次重新渲染,光标都会重新设为0,然后从对应的数组中读取状态和set函数
4 事件处理
每次调用set函数时,set函数将会修改state数组中对应的状态值,这种对应的关系是通过cursor光标来确定的
那篇文章的作者简单的实现了一下"简陋"的hooks:
let state = [];
let setters = [];
let firstRun = true;
let cursor = 0;
function createSetter(cursor) {
return function setterWithCursor(newVal) {
state[cursor] = newVal;
};
}
// This is the pseudocode for the useState helper
export function useState(initVal) {
if (firstRun) {
state.push(initVal);
setters.push(createSetter(cursor));
firstRun = false;
}
const setter = setters[cursor];
const value = state[cursor];
cursor++;
return [value, setter];
}
// Our component code that uses hooks
function RenderFunctionComponent() {
const [firstName, setFirstName] = useState("Rudi"); // cursor: 0
const [lastName, setLastName] = useState("Yardley"); // cursor: 1
return (
<div>
<Button onClick={() => setFirstName("Richard")}>Richard</Button>
<Button onClick={() => setFirstName("Fred")}>Fred</Button>
</div>
);
}
// This is sort of simulating Reacts rendering cycle
function MyComponent() {
cursor = 0; // resetting the cursor
return <RenderFunctionComponent />; // render
}
console.log(state); // Pre-render: []
MyComponent();
console.log(state); // First-render: ['Rudi', 'Yardley']
MyComponent();
console.log(state); // Subsequent-render: ['Rudi', 'Yardley']
// click the 'Fred' button
console.log(state); // After-click: ['Fred', 'Yardley']
看完简单的原理,我们来解释一下为什么不能在循环,条件判断,嵌套函数中使用hooks:
let firstRender = true;
function RenderFunctionComponent() {
let initName;
if(firstRender){
[initName] = useState("Rudi");
firstRender = false;
}
const [firstName, setFirstName] = useState(initName);
const [lastName, setLastName] = useState("Yardley");
return (
<Button onClick={() => setFirstName("Fred")}>Fred</Button>
);
}
这段代码在条件渲染中使用hooks,第一次渲染时,内部的两个数组映射关系如下:
我们可以看到firstName和setFirstName、lastName和setLastName有正确的对应关系,但是第二次渲染时:
firstName的值是initName,lastName的值是fistName,这种映射的关系就混乱了,所以这就是不要在循环,条件判断,嵌套函数中使用hooks的原因。
参考: