react焕发第二春,函数式编程HOOKS

文章目录

    • HOOKS
    • Hook 使用规则
    • useState():状态钩子
    • useEffect():副作用钩子
      • 副效应是什么?
      • useEffect() 的用法
      • useEffect() 的用途
    • useContext():共享状态钩子
    • useReducer():action 钩子
    • 创建自己的 Hooks
    • Hook API

HOOKS

Hook 是 React 16.8 的新增特性。它可以让你在不编写 class 的情况下使用 state 以及其他的 React 特性

Hook 是一些可以让你在函数组件里“钩入” React state 及生命周期等特性的函数

Hook 这个单词的意思是"钩子"。

React Hooks 的意思是,组件尽量写成纯函数,如果需要外部功能和副作用,就用钩子把外部代码"钩"进来。 React Hooks 就是那些钩子。

你需要什么功能,就使用什么钩子。React 默认提供了一些常用钩子,你也可以封装自己的钩子。

所有的钩子都是为函数引入外部功能,所以 React 约定,钩子一律使用use前缀命名,便于识别。你要使用 xxx 功能,钩子就命名为 usexxx。

Hook 使用规则

Hook 就是 JavaScript 函数,但是使用它们会有两个额外的规则:

  • 只能在函数最外层调用 Hook。不要在循环、条件判断或者子函数中调用。
  • 只能在 React 的函数组件和自定义hook中调用 Hook。不要在其他 JavaScript 函数中调用。

useState():状态钩子

useState()用于为函数组件引入状态(state)。纯函数不能有状态,所以把状态放在钩子里面。

useState()这个函数接受状态的初始值,作为参数,上例的初始值为按钮的文字。该函数返回一个数组,数组的第一个成员是一个变量(上例是buttonText),指向状态的当前值。第二个成员是一个函数,用来更新状态,约定是set前缀加上状态的变量名(上例是setButtonText

import React, { useState } from "react"

function Example() {
	// 声明一个新的叫做 “count” 的 state 变量
	const [count, setCount] = useState(0);
  
  // 声明多个 state 变量!
  const [age, setAge] = useState(42);
  const [fruit, setFruit] = useState('banana');
  const [todos, setTodos] = useState([{ text: 'Learn Hooks' }]);

	return (
		<div>
			<p>You clicked {count} times</p>
			<button onClick={() => setCount(count + 1)}>
				Click me
			</button>
		</div>
	);
}
  • 调用 useState 方法的时候做了什么? 它定义一个 “state 变量”
  • useState 需要哪些参数? useState() 方法里面唯一的参数就是初始 state
  • useState 方法的返回值是什么? 返回值为:当前 state 以及更新 state 的函数

useEffect():副作用钩子

function Welcome(props) {
 return <h1>Hello, {props.name}</h1>;
}

这个函数只做一件事,就是根据输入的参数,返回组件的 HTML 代码。这种只进行单纯的数据计算(换算)的函数,在函数式编程里面称为 “纯函数”(pure function)。

副效应是什么?

看到这里,你可能会产生一个疑问:如果纯函数只能进行数据计算,那些不涉及计算的操作(比如生成日志、储存数据、改变应用状态等等)应该写在哪里呢?

函数式编程将那些跟数据计算无关的操作,都称为 “副效应” (side effect) 。如果函数内部直接包含产生副效应的操作,就不再是纯函数了,我们称之为不纯的函数。

纯函数内部只有通过间接的手段(即通过其他函数调用),才能包含副效应。

useEffect() 的用法

useEffect()本身是一个函数,由 React 框架提供,在函数组件内部调用即可。

举例来说,我们希望组件加载以后,网页标题(document.title)会随之改变。那么,改变网页标题这个操作,就是组件的副效应,必须通过useEffect()来实现。

useEffect()的作用就是指定一个副效应函数,组件每渲染一次,该函数就自动执行一次。组件首次在网页 DOM 加载后,副效应函数也会执行。

React 组件中数据获取、订阅或者手动修改过 DOM。我们统一把这些操作称为“副作用”,或者简称为“作用”。

useEffect 就是一个 Effect Hook,给函数组件增加了操作副作用的能力。它跟 class 组件中的 componentDidMountcomponentDidUpdatecomponentWillUnmount 具有相同的用途,只不过被合并成了一个 API

import React, { useState, useEffect } from 'react';

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>
}

由于副作用函数是在组件内声明的,所以它们可以访问到组件的 props 和 state

  • useEffect 做了什么? 通过使用这个 Hook,你可以告诉 React 组件需要在渲染后执行某些操作
  • 为什么在组件内部调用 useEffectuseEffect 放在组件内部让我们可以在 effect 中直接访问 count state 变量(或其他 props)
  • useEffect 会在每次渲染后都执行吗? 是的,默认情况下,它在第一次渲染之后每次更新之后都会执行
  • 为什么要在 effect 中返回一个函数? 这是 effect 可选的清除机制。每个 effect 都可以返回一个清除函数
  • React 何时清除 effect? React 会在组件卸载的时候执行清除操作
  • useEffect()允许返回一个函数,在组件卸载时,执行该函数,清理副效应。如果不需要清理副效应,useEffect()就不用返回任何值。
  • 使用useEffect()时,有一点需要注意。如果有多个副效应,应该调用多个useEffect(),而不应该合并写在一起。
function App() {
	const [varA, setVarA] = useState(0);
	const [varB, setVarB] = useState(0);
	
	useEffect(() => {
	 const timeout = setTimeout(() => setVarA(varA + 1), 1000);
	 return () => clearTimeout(timeout);
	}, [varA]);
	
	useEffect(() => {
	 const timeout = setTimeout(() => setVarB(varB + 2), 2000);
	
	 return () => clearTimeout(timeout);
	}, [varB]);
	
	return <span>{varA}, {varB}</span>;
}

useEffect() 的用途

只要是副效应,都可以使用useEffect()引入。它的常见用途有下面几种。

  • 获取数据(data fetching)
  • 事件监听或订阅(setting up a subscription)
  • 改变 DOM(changing the DOM)
  • 输出日志(logging)

useContext():共享状态钩子

如果需要在组件之间共享状态,可以使用useContext()

现在有两个组件 Navbar 和 Messages,我们希望它们之间共享状态。

<div className="App">
	<Navbar/>
	<Messages/>
</div>

第一步就是使用 React Context API,在组件外部建立一个 Context。

const AppContext = React.createContext({});

组件封装代码如下。

<AppContext.Provider value={{
  username: 'superawesome'
}}>
<div className="App">
 <Navbar/>
 <Messages/>
div>
AppContext.Provider>

上面代码中,AppContext.Provider提供了一个 Context 对象,这个对象可以被子组件共享。

Navbar 组件的代码如下。

const Navbar = () => {
const { username } = useContext(AppContext);
return (
	 <div className="navbar">
	  <p>AwesomeSite</p>
	  <p>{username}</p>
	 </div>
 );
}

上面代码中,useContext()钩子函数用来引入 Context 对象,从中获取username属性。

Message 组件的代码也类似。

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 功能。

const [state, dispatch] = useReducer(reducer, initialState);

上面是useReducer()的基本用法,它接受 Reducer 函数和状态的初始值作为参数,返回一个数组。数组的第一个成员是状态的当前值,第二个成员是发送 action 的dispatch函数。

下面是一个计数器的例子。用于计算状态的 Reducer 函数如下。

const myReducer = (state, action) => {
switch(action.type)  {
	case('countUp'):
	  return  {
	   ...state,
	    count: state.count + 1
	  }
	default:
	  	return  state;
	}
}

组件代码如下。

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>
 );
}

创建自己的 Hooks

上例的 Hooks 代码还可以封装起来,变成一个自定义的 Hook,便于共享。

const usePerson = (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]);  
 return [loading, person];
 };

上面代码中,usePerson()就是一个自定义的 Hook。

Person 组件就改用这个新的钩子,引入封装的逻辑。

 const Person = ({ personId }) => {
 const [loading, person] = usePerson(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>
 );
};

Hook API

react焕发第二春,函数式编程HOOKS_第1张图片

你可能感兴趣的:(#,React,react.js,javascript,前端,hooks,函数式编程)