咱们知道,React 提供的单向数据流以及组件化帮助咱们将一个庞大的项目变为小型、独立、可复用的组件。但有时,咱们没法进一步拆分很复杂的组件,由于它们内部的逻辑是有状态的,没法抽象为函数式组件。因此有时咱们可能会写出很是不适合复用性开发的:react
但谢天谢地,Hooks 的出现,让咱们把组件内部的逻辑组织成为了可复用的隔离单元。api
跨组件地复用包含状态的逻辑,经过 Hooks 能够将含有 state 的逻辑从组建抽象出来,同时也能够帮助咱们在不重写组件结构的状况下复用逻辑。Hooks 通常是用于函数式组件的,在类class组件中无效。让咱们根据代码的做用将它们拆分,而不是生命周期。简而言之, Hooks 实现了咱们在函数式组件中使用状态变量与相似于生命周期的操做。数组
useState
建立Hookimport {useState} from 'react';
function hooks(){
// 声明一个名为 count 的新状态变量
const [count, setCount] = useState(0);
// 第二个参数 setCount 为一个能够更新状态的函数
// useState 的参数即为初始值
return (
<div>
<p>当前的状态量为: {count}</p>
<button onClick={() => setCount(count + 1)}>点击加一</button>
</div>
)
}
复制代码
useEffect
来执行相应操做import {useState, useEffect} from 'react';
function hooks(){
const [count, setCount] = useState(0);
// 相似于 componentDidMount 和 componentDidUpdate
// 在 useEffect 中可使用组建的 state 和 props
// 在每次渲染后都执行 useEffect
useEffect(() => {
window.alert(`You have clicked ${count} times`);
})
return (
<div>
<p>当前的状态量为: {count}</p>
<button onClick={() => setCount(count + 1)}>点击加一</button>
</div>
)
}
复制代码
咱们在两个不一样的组件使用同一个钩子,他们是相互独立的,甚至在一个组件使用两个钩子他们也是相互独立的。浏览器
React 实际上是根据useState
传出现的顺序来保证useState
之间相互独立。缓存
// 首次渲染
const [num, setNum] = useState(1); // 将num初始化为1
const [str, setStr] = useState('string'); // 将str初始化为'string'
const [obj, setObj] = useState({id:1}); // ....
// 第二次渲染
const [num, setNum] = useState(1); // 读取状态变量num的值, 此时传入的参数已被忽略,下同
const [str, setStr] = useState('string'); // 读取状态变量str的值
const [obj, setObj] = useState({id:1}); // ....
复制代码
同时正是因为根据顺序保证独立,因此 React 规定咱们必须把 hooks 写在最外层,而不能写在条件语句之中,来确保hooks的执行顺序一致,若要进行条件判断,咱们应该在 useEffect
的函数中写入条件闭包
useEffect 来传递给 React 一个方法,React会在进行了 DOM 更新以后调用。咱们一般将 useEffect 放入组件内部,这样咱们能够直接访问 state 与 props。记得,useEffect 在每次 render 后都要调用。异步
咱们有时须要从外部数据源获取数据,此时咱们就要保证清理Effect来避免内存泄露 ,此时咱们须要在 effect 中返回一个函数来清理它, React 会在组件每次接触挂载的时候清理。一个比较使用的场景就是咱们在 useEffect
中若执行了异步请求,因为异步的时间不肯定性,咱们很须要在执行下一次异步请求时先结束上一次的请求,所以咱们就须要清理。async
useEffect(() => {
let canceled = false;
const getData = async () => {
const res = await fetch(api);
if(!canceled) {
// 展现 res
}
}
getData();
// return 的即为咱们的清理函数
return () => {
canceled = true;
}
});
复制代码
此时咱们在进行从新渲染时,就能够避免异步请求带来的竞态问题,从而避免数据的不稳定性。函数
咱们能够给useEffect
传入第二个参数只有当第二个参数(数组)里的全部的state 值发生变化时,才从新执行Effect组件化
useEffect(() => {
window.alert(`you had clicked ${count} times`);
}, [count]); //只有当 count 发生变化时才会从新执行effect
复制代码
因为函数式组件中没有 this ,因此咱们没法使用ref,但hooks帮助咱们解决了这个问题,他提供了useRef
方法来为咱们建立一个实例,而传入的参数会被挂载在这个实例的.current
属性上,返回的实例会持续到整个生命周期结束为止。
function RefExample() {
const ref1 = useRef(null);
return (
<div>
<input ref={ref1} type="text" />
<button onClick={() => {ref1.current.focus()}}
</div>
)
}
复制代码
若是比起上面的状态变量类型,你更想要使用 Redux 类型的状态管理,OK,React 也给咱们提供了useReducer
这个方法。做为useState
的一种替代,咱们可使用dispatch
方法来改变状态变量。
// 初始化的状态变量
const initState = {count:0};
// 编写 reducer 处理函数
function reducer(state, action) {
switch(action.type) {
case 'increment': return {count: state.count + 1};
case 'decrement': return {count: state.count - 1};
}
}
function counter({initState}) {
const [state, dispatch] = useReducer(reducer, initState);
return (
<div>
<p>Count: {state.count}</p>
<button onClick={() => dispatch({type: 'increment'})}>+</button>
<button onClick={() => dispatch({type: 'decrement'})}>-</button>
</div>
)
}
复制代码
咱们能够经过监听状态变量并在变换后执行回调函数来执行 Effect ,此时你可能会问,为何使用 Hooks 会使用这么多的 inline 函数,岂不是很影响性能? 谢天谢地,JavaScript 中的闭包函数的性能十分的快,它帮助了咱们不少。回调形式的 Hooks 有两种,useCallback
与useMemo
.
两者的转换关系为:
useCallback(fn, inputs) === useMemo(() => fn, inputs)
useCallback
是如何帮助咱们提高性能的呢? 实际上,它实际上是缓存了每次渲染时的 inline 回调函数的实例,以后不管是配合shouldComponentUpdate
或者是 React.memo
都可以达到减小没必要要的渲染的做用。这也提示咱们,React.memo
和React.useCallback
通常是配合使用,缺了其一均可能没法达到提高性能的功效。
下面以一个表单组件表示使用方法
function FormComponent() {
const [text, setText] = useState(' ');
const handleSubmit = useCallback(() => {
console.log(`new test is ${text}`);
}, [text]);
return (
<div>
<input value={text} onChange={(e) => setText(e.target.value)} />
<BigTree onSubmit={handleSubmit} /> // 巨大无比的组件,不优化卡的不行
</div>
)
}
复制代码
但此时有一个很严重的问题,就是咱们的 BigTree 依赖于一个太容易变化的 state, 只要咱们在input框随意输入, BigTree 就会从新渲染好屡次来获取最新的callback,此时这个callback就没法使用缓存了。
一个解决办法是咱们定义一个新的实例,这个实例只有在 re-render 时才会更新最新的值,这样咱们就能够不根据一个常常变换的state,而是根据一个在 useLayoutEffect
中更新的ref实例来更新。
function FormComponent() {
const [text, setText] = useState(' ');
const textRef = useRef();
useLayoutEffect(() => {
textRef.current = text;
})
const handleSubmit = useCallback(() => {
console.log(`new test is ${text}`);
}, [textRef]); // 只根据 textRef 的变化而产生变化,并不会在 text 改变就变化
return (
<div>
<input value={text} onChange={(e) => setText(e.target.value)} />
<BigTree onSubmit={handleSubmit} /> // 巨大无比的组件,不优化卡的不行
</div>
)
}
复制代码
DOM 突变以后,从新绘制以前同步触发
它与 useEffect
的做用相同,都是用来执行反作用的,但不一样的是,它会在全部的 DOM 变动结束后同步地调用 effect。一个与 useEffect
很大的区别是,useLayoutEffect
是同步地,而useEffect
是异步的,在浏览器从新绘制页面布局前,useLayoutEffect
内部的更新将会同步刷新,但官方给出的建议是尽可能使用useEffect
来避免阻塞视觉更新。
render props
与 高阶组件
时产生的夸张的层级嵌套。