React Hooks
功能介绍
对函数组件进行增强,让函数组件可以存储状态,可以拥有处理副作用的能力。
使开发者在不使用类组件的情况下,实现相同的功能。
副作用:组件中只要不是把组件转换为视图的代码,都是副作用(定时器、设置 DOM 元素的点击事件等等)。
类组件的不足
1. 缺少逻辑复用机制
为了复用逻辑增加无实际渲染效果的组件,增加了组件层级,显得十分臃肿。增加了调试的难度以及运行效率的降低。
2. 类组件经常变得复杂难以维护
将一组相干的业务逻辑拆分到多个生命周期函数中。
在一个生命周期内存在多个不相干的业务逻辑。
3. 类成员方法不能保证 this 指向的正确性
useState
Hooks 即钩子,React Hooks 就是一堆钩子函数,React 通过这些钩子函数对函数型组件进行增强,不同的钩子函数提供不同的功能。
useState()
useEffect()
useReducer()
useRef()
useCallback()
useContext()
useMemo()
基础使用
useState 为函数组件引入状态。
import React, { useState } from 'react';
function App () {
const [count, setCount] = useState(0);
return (
<div>
<span>{ count }</span>
<button onClick={() => setCount(count + 1)}>+1</button>
</div>
);
}
export default App;
使用细节
接收唯一的参数即状态初始值,初始值可以是任意数据类型。
返回值为数组,数组中存储状态值和更改状态值的方法,方法名称约定以 set 开头,后面加上状态名称。
方法可以被调用多次,用以保存不同状态值。
参数可以是一个函数,函数返回什么,初始状态就是什么,函数只会被调用一次,用在初始值是动态值的情况。
设置状态值方法的参数可以是一个值也可以是一个函数。
设置状态值方法的方法本身是异步的。
setCount(count => {
const newCount = count + 1;
document.title = newCount;
return newCount;
})
useReducer
useReducer 是一种让函数组件保存状态的方式。
import React, { useReducer } from 'react';
function reducer (state, action) {
switch (action.type) {
case 'increment':
return state + 1;
case 'decrement':
return state - 1;
default:
return state;
}
}
function App () {
const [count, dispatch] = useReducer(reducer, 0);
return (
<div>
<button onClick={() => dispatch({ type: 'decrement' })}>-1</button>
<span>{ count }</span>
<button onClick={() => dispatch({ type: 'increment' })}>+1</button>
</div>
);
}
export default App;
useContext
跨组件层级获取数据时简化获取数据的代码。
import React, { createContext } from 'react';
const countContext = createContext();
function Foo () {
return (
<countContext.Consumer>
{
(value) => (
<div>{ value }</div>
)
}
</countContext.Consumer>
)
}
function App () {
return (
<countContext.Provider value={ 100 }>
<Foo />
</countContext.Provider>
)
}
export default App;
简化操作如下:
import React, { createContext, useContext } from 'react';
const countContext = createContext();
function Foo () {
const value = useContext(countContext);
return (
<div>{ value }</div>
)
}
function App () {
return (
<countContext.Provider value={ 100 }>
<Foo />
</countContext.Provider>
)
}
export default App;
useEffect
让函数型组件拥有处理副作用的能力,类似生命周期函数。
基础使用
useEffect 看作是 componentDidMount、componentDidUpdate 和 componentWillUnmount 这三个函数的组合。
useEffect(() => {}); => componentDidMount、componentDidUpdate
useEffect(() => {}, []); => componentDidMount
useEffect(() => () => {}); => componentWillUnmount
import React, { useState, useEffect } from 'react';
import ReactDOM from 'react-dom';
function App () {
const [count, setCount] = useState(0);
useEffect(() => {
console.log(count);
});
useEffect(() => {
console.log(count);
}, [])
useEffect(() => {
return () => {
console.log('unmount:', count);
}
})
return (
<div>
<span>{ count }</span>
<button onClick={() => setCount(count + 1)}>+1</button>
<button onClick={() => ReactDOM.unmountComponentAtNode(document.getElementById('root'))}>卸载组件</button>
</div>
);
}
export default App;
使用细节
window 对象添加滚动事件、设置定时器让 count 数值每隔一秒增加 1
import React, { useEffect, useState } from 'react';
import ReactDOM from 'react-dom';
function App () {
function onScroll () {
console.log('change');
}
useEffect(() => {
window.addEventListener('scroll', onScroll);
return () => {
window.removeEventListener('scroll', onScroll);
}
}, []);
const [count, setCount] = useState(0);
useEffect(() => {
const timerId = setInterval(() => {
setCount((count) => count + 1);
}, 1000);
return () => {
clearInterval(timerId);
}
}, []);
return (
<div>
<span>works</span>
<span>{ count }</span>
<button onClick={() => ReactDOM.unmountComponentAtNode(document.getElementById('root'))}>卸载组件</button>
</div>
);
}
export default App;
优点
可以将按照用途将代码进行分类(将一组相干的业务逻辑归置到同一个副作用函数中)。
简化重复代码,使组件内部代码更加清晰。
数据检测
useEffect 第二个参数是一个数组,可以指定依赖项。只有指定依赖数据发生变化时会触发 effect。
import React, { useEffect, useState } from 'react';
function App () {
const [count, setCount] = useState(0);
useEffect(() => {
document.title = count;
}, [count]);
return (
<div>
<span>{ count }</span>
<button onClick={ () => setCount(count => count + 1) }>+1</button>
</div>
);
}
export default App;
结合异步函数
useEffect 中的参数函数不能是异步函数,因为 useEffect 函数要返回清理资源的函数,如果是异步函数就变成了返回 Promise。
import React, { useEffect } from 'react';
function getData () {
return new Promise(resolve => {
resolve({ message: 'hello' });
});
}
function App () {
useEffect(() => {
(async () => {
const result = await getData();
console.log(result);
})();
}, []);
return (
<div>
</div>
);
}
export default App;
useMemo
useMemo 类似于 Vue 中的计算属性,可以检测某个值的变化,根据变化值计算新值。useMemo 会缓存计算结果,如果检测值没有发生变化,即使组件重新渲染,也不会重新计算。此行为可以有助于避免在每个渲染上进行大量计算。
import React, { useMemo, useState } from 'react';
function App () {
const [count, setCount] = useState(0);
const [bool, setBool] = useState(true);
const result = useMemo(() => {
console.log(1);
return count * 2;
}, [count])
return (
<div>
<span>{ result }</span> - <span>{ count }</span>
<span>{ bool ? '真' : '假' }</span>
<button onClick={ () => setCount(count + 1) }>Add</button>
<button onClick={ () => setBool(!bool) }>setBool</button>
</div>
);
}
export default App;
memo
性能优化,如果本组件中的数据没有发生变化,阻止组件更新,类似类组件中 PureComponent 和 shouldComponentUpdate。
import React, { useState, memo } from 'react';
const Foo = memo(function Foo () {
console.log('foo render');
return (
<div>Foo 组件</div>
)
});
function App () {
const [count, setCount] = useState(0);
return (
<div>
<span>{ count }</span>
<button onClick={ () => setCount(count + 1) }>Add</button>
<Foo />
</div>
);
}
export default App;
useCallback
性能优化,缓存函数,组件重新渲染时得到相同的函数实例。
import React, { useState, memo, useCallback } from 'react';
const Foo = memo(function Foo (props) {
console.log('foo render');
return (
<div>
<span>Foo 组件</span>
<button onClick={ props.resetCount }>resetCount</button>
</div>
)
});
function App () {
const [count, setCount] = useState(0);
const resetCount = useCallback(() => setCount(0), [setCount]);
return (
<div>
<span>{ count }</span>
<button onClick={ () => setCount(count + 1) }>Add</button>
<Foo resetCount={ resetCount } />
</div>
);
}
export default App;
使用 useCallback 可以避免在父组件重新渲染时,导致 memo 缓存的子组件缓存失效。
useRef
获取 DOM 元素对象。
基本使用
import React, { useRef } from 'react';
function App () {
const ref = useRef();
return (
<div
ref={ ref }
>
App works
<button onClick={ () => console.log(ref.current) }>获取 DIV</button>
</div>
);
}
export default App;
保存数据
useRef 保存的数据是跨组件周期的,即使组件重新渲染,保存的数据仍然还在,保存的数据被更改不会触发组件重新渲染。
import React, { useState, useEffect, useRef } from 'react';
function App () {
const [count, setCount] = useState(0);
let timerId = useRef();
useEffect(() => {
timerId.current = setInterval(() => {
setCount(count => count + 1);
}, 1000)
}, []);
const stopCount = () => {
clearInterval(timerId.current);
}
return (
<div>
{ count }
<button onClick={stopCount}>停止</button>
</div>
);
}
export default App;
自定义 hook
自定义 hook 是标准的封装和共享逻辑的方式。
自定义 hook 是一个函数,其名称以 use 开头。
自定义 hook 其实就是逻辑和内置 Hook 的组合。
import React, { useEffect, useState } from 'react';
import axios from 'axios';
function useGetPost () {
const [post, setPost] = useState({});
useEffect(() => {
axios.get('https://jsonplaceholder.typicode.com/posts/1')
.then(res => {
setPost(res.data);
})
.catch(err => console.log(err));
}, []);
return [post, setPost];
}
function App () {
const [post] = useGetPost();
return (
<div>
<div>{ post.title }</div>
<div>{ post.body }</div>
</div>
);
}
export default App;
import React, { useEffect, useState } from 'react';
const useUpdateInput = (initialValue) => {
const [value, setValue] = useState(initialValue);
return {
value,
onChange: event => setValue(event.target.value)
}
}
function App () {
const usenameInput = useUpdateInput('');
const passwordInput = useUpdateInput('');
const submitForm = (e) => {
e.preventDefault();
console.log(usenameInput.value, passwordInput.value);
}
return (
<form onSubmit={ submitForm }>
<input type="text" name="username" { ...usenameInput } />
<input type="text" name="password" { ...passwordInput } />
<button type="submit">提交</button>
</form>
);
}
export default App;
路由钩子函数
react-router-dom 路由提供的钩子函数。
useHistory、useLocation、useRouteMatch、useParams
npm install react-router react-router-dom@5.1.0
src/index.js
import React from 'react';
import ReactDOM from 'react-dom';
import { BrowserRouter as Router } from 'react-router-dom';
import './index.css';
import App from './App';
ReactDOM.render(
<Router>
<App />
</Router>,
document.getElementById('root')
);
src/App.js
import React from 'react';
import { Link, Route } from 'react-router-dom';
import Home from './pages/Home';
import List from './pages/List';
function App () {
return (
<>
<div>
<Link to="/home">首页</Link>
<Link to="/list">列表页</Link>
</div>
<div>
<Route path="/home" component={ Home } />
<Route path="/list" component={ List } />
</div>
</>
);
}
export default App;
src/pages/Home.js
import React from 'react';
import { useHistory, useLocation, useRouteMatch, useParams } from 'react-router-dom';
export default function Home (props) {
console.log(props);
console.log(useHistory())
console.log(useLocation())
console.log(useRouteMatch())
console.log(useParams())
return <div>Home</div>;
}
src/pages/List.js
import React from 'react';
export default function List (props) {
console.log(props);
return <div>List</div>;
}
useState 实现
import React from 'react';
import ReactDOM from 'react-dom';
let state = [];
let setters = [];
let stateIndex = 0;
function render () {
stateIndex = 0;
ReactDOM.render(<App />, document.getElementById('root'));
}
function createSetter (index) {
return function (newVal) {
state[index] = newVal;
render();
}
}
function useState (initialState) {
state[stateIndex] = state[stateIndex] ? state[stateIndex] : initialState;
setters.push(createSetter(stateIndex))
const value = state[stateIndex];
const setter = setters[stateIndex];
stateIndex++;
return [value, setter];
}
function App () {
const [count, setCount] = useState(0);
const [name, setName] = useState('yueluo');
return (
<>
<p>{ count }</p>
<button onClick={() => setCount(count + 1)}>setCount</button>
<p>{ name }</p>
<button onClick={() => setName('heore')}>setName</button>
</>
);
}
export default App;
useEffect 实现
import React from 'react';
import ReactDOM from 'react-dom';
let state = [];
let setters = [];
let stateIndex = 0;
function render () {
stateIndex = 0;
effectIndex = 0;
ReactDOM.render(<App />, document.getElementById('root'));
}
function createSetter (index) {
return function (newVal) {
state[index] = newVal;
render();
}
}
function useState (initialState) {
state[stateIndex] = state[stateIndex] ? state[stateIndex] : initialState;
setters.push(createSetter(stateIndex))
const value = state[stateIndex];
const setter = setters[stateIndex];
stateIndex++;
return [value, setter];
}
let prevDeps = [];
let effectIndex = 0;
function useEffect (callback, deps) {
const toStr = Object.prototype.toString;
if (toStr.call(callback) !== '[object Function]') {
throw new Error('useEffect 第一个参数必须是函数');
}
if (typeof deps === 'undefined') {
callback();
} else {
if (toStr.call(deps) !== '[object Array]') {
throw new Error('useEffect 第二个参数必须是数组');
}
// 获取上一次的状态值
const _prevDeps = prevDeps[effectIndex];
// 判断值是否存在变化
const hasChanged = _prevDeps ? !deps.every((dep, index) => dep === _prevDeps[index]) : true
if (hasChanged) {
callback();
}
prevDeps[effectIndex] = deps;
effectIndex++;
}
}
function App () {
const [count, setCount] = useState(0);
const [name, setName] = useState('yueluo');
useEffect(() => {
console.log('hello');
}, [ count ])
useEffect(() => {
console.log('world');
}, [ name ])
return (
<>
<p>{ count }</p>
<button onClick={() => setCount(count + 1)}>setCount</button>
<p>{ name }</p>
<button onClick={() => setName('heora')}>setName</button>
</>
);
}
export default App;