React class 类组件的缺点
大型组件很难拆分和重构,也很难测试
业务逻辑分散在组件的各个方法之中,导致重复逻辑或关联逻辑
组件类引入了复杂的编程模式,比如 render props 和高阶组件,(再加入 Redux,就变得更复杂)
why hooks
React Hooks 的设计目的,就是加强版函数组件,完全不使用”类”,就能写出一个全功能的组件。
React Hooks 的意思是,组件尽量写成纯函数,如果需要外部功能和副作用,就用钩子把外部代码”钩”进来。 React Hooks 就是那些钩子。
React 16.8 之前组件的书写方式
ES5
React.createClass
ES6
class xxxx extends Component
无状态
function
React 16.8 之后逐渐采用 hooks
1 2 3 4 5 6 7 8 9 10 11
| import React, { useState } from "react";
export default function Button() { const [buttonText, setButtonText] = useState("Click me, please");
function handleClick() { return setButtonText("Thanks, been clicked!"); }
return <button onClick={handleClick}>{buttonText}</button>; }
|
React hooks
- 钩子一律使用 use 前缀命名,便于识别。
- React 默认提供的四个最常用的钩子。
1 2 3 4
| useState(); useContext(); useReducer(); useEffect();
|
useState():状态钩子
useState()用于为函数组件引入状态(state)。纯函数不能有状态,所以把状态放在钩子里面。
1 2 3 4 5 6 7 8 9 10 11
| import React, { useState } from "react";
export default function Button() { const [buttonText, setButtonText] = useState("Click me, please");
function handleClick() { return setButtonText("Thanks, been clicked!"); }
return <button onClick={handleClick}>{buttonText}</button>; }
|
useState()这个函数接受状态的初始值,作为参数,上例的初始值为按钮的文字。该函数返回一个数组,数组的第一个成员是一个变量(上例是 buttonText),指向状态的当前值。第二个成员是一个函数,用来更新状态,约定是 set 前缀加上状态的变量名(上例是 setButtonText)
useContext():共享状态钩子
需要在组件之间共享状态,可以使用 useContext()
- 现在有两个组件 Navbar 和 Messages,我们希望它们之间共享状态。
1 2 3 4
| <div className="App"> <Navbar /> <Messages /> </div>
|
- 第一步就是使用 React Context API,在组件外部建立一个 Context。
1
| const AppContext = React.createContext({});
|
1 2 3 4 5 6 7 8 9 10
| <AppContext.Provider value={{ username: "superawesome", }} > <div className="App"> <Navbar /> <Messages /> </div> </AppContext.Provider>
|
- 上面代码中,AppContext.Provider 提供了一个 Context 对象,这个对象可以被子组件共享。
- Navbar 组件的代码如下。
1 2 3 4 5 6 7 8 9
| const Navbar = () => { const { username } = useContext(AppContext); return ( <div className="navbar"> <p>AwesomeSite</p> <p>{username}</p> </div> ); };
|
- 上面代码中,useContext()钩子函数用来引入 Context 对象,从中获取 username 属性。
- Message 组件的代码也类似。
1 2 3 4 5 6 7 8 9 10 11
| const Messages = () => { const { username } = useContext(AppContext);
return ( <div className="messages"> <h1>Messages</h1> <p>1 message for {username}</p> <p className="message">useContext is awesome!</p> </div> ); };
|
useReducer():action 钩子
React 本身不提供状态管理功能,通常需要使用外部库。这方面最常用的库是 Redux。
Redux 的核心概念是,组件发出 action 与状态管理器通信。状态管理器收到 action 以后,使用 Reducer 函数算出新的状态,Reducer 函数的形式是(state, action) => newState。
useReducers()钩子用来引入 Reducer 功能。
1
| const [state, dispatch] = useReducer(reducer, initialState);
|
上面是 useReducer()的基本用法,它接受 Reducer 函数和状态的初始值作为参数,返回一个数组。数组的第一个成员是状态的当前值,第二个成员是发送 action 的 dispatch 函数。
下面是一个计数器的例子。用于计算状态的 Reducer 函数如下。
1 2 3 4 5 6 7 8 9 10 11
| const myReducer = (state, action) => { switch (action.type) { case "countUp": return { ...state, count: state.count + 1, }; default: return state; } };
|
1 2 3 4 5 6 7 8 9
| function App() { const [state, dispatch] = useReducer(myReducer, { count: 0 }); return ( <div className="App"> <button onClick={() => dispatch({ type: "countUp" })}>+1</button> <p>Count: {state.count}</p> </div> ); }
|
useEffect():副作用钩子
useEffect()用来引入具有副作用的操作,最常见的就是向服务器请求数据。以前,放在 componentDidMount 里面的代码,现在可以放在 useEffect()。
useEffect()的用法如下。
1 2 3
| useEffect(() => { }, [dependencies]);
|
上面用法中,useEffect()接受两个参数。第一个参数是一个函数,异步操作的代码放在里面。第二个参数是一个数组,用于给出 Effect 的依赖项,只要这个数组发生变化,useEffect()就会执行。第二个参数可以省略,这时每次组件渲染时,就会执行 useEffect()。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| const Person = ({ personId }) => { const [loading, setLoading] = useState(true); const [person, setPerson] = useState({});
useEffect(() => { setLoading(true); fetch(`https://swapi.co/api/people/${personId}/`) .then(response => response.json()) .then(data => { setPerson(data); setLoading(false); }); }, [personId])
if (loading === true) { return <p>Loading ...</p> }
return <div> <p>You're viewing: {person.name}</p> <p>Height: {person.height}</p> <p>Mass: {person.mass}</p> </div>
|
- 上面代码中,每当组件参数 personId 发生变化,useEffect()就会执行。组件第一次渲染时,useEffect()也会执行。