在之间的版本中,statelifecycle functions等这些功能只能在类组件中使用,这使得函数组件的功能和使用被大大的限制了。

但是自从引入React Hooks,函数组件地位大大提升,跟类组件并驾齐驱,甚至官方更推荐使用简洁的函数组件。所以除了之前介绍的类组件,我们更应该多了解函数组件的特性,因为在真实项目中它被使用的越来越多。

基于函数组件

在之前的文章中我们提到了两种组件定义的方法,分别使用classfunction 语法:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
import React from 'react';

class MyClassComponent extends React.Component {
  render(){
    return <h1>Hello World</h1>
  }
}

function MyFunctionComponent(){
  return <h1>Hello World</h1>
}

函数组件不像类组件,继承了React的Component,所以它没办法得到state,也没法通过生命周期函数来在不同的阶段执行代码。

另外一个不同之处在于类组件将接收到的props存在this.props对象中,而函数组件需要通过第一个参数来接收props:

1
2
3
function MyFunctionComponent(props){
  return <h1> Hello World</h1>
}

下面是一个类组件调用函数组件的例子:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
import React from 'react';

class MyClassComponent extends React.Component {
  render(){
    return <MyFunctionComponent name="John">
  }
}

function MyFunctionComponent(props){
  return <h1>Hello World, {props.name}</h1>
}

useState hook

React hooks通俗讲就是React包提供的一系列函数,它可以给组件添加额外的功能。值得注意的是,Hooks只能在函数组件中使用,类组件没法用。

React提供了10多个hooks,不过一般在我们写组件的时候最常用的也就那么两三个。我常用到的hooks是useStateuseEffect

useStatehook接收一个参数,用来做状态初始化;返回两个值:当前状态和更新状态的函数。

1
2
3
4
5
import React, {useState} from 'react'

function UserComponent(){
  const [name, setName] = useState('John')
}

这里用到了ES6的array destructuring syntax,意思就是将函数useState返回的第一个值赋值给name,第二个值赋值给setName(注意它是一个函数),使用方法:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
import React, {useState} from 'react'

function UserComponent(){
  const [name, setName] = useState('John')
  
  if(name === "John"){
    setName("Luke")
  }
  
  return <h1>Hello World! My name is {name}</h1>
}

这里我们能够直接使用name变量,并且使用setName函数来改变它的值,功能类似与之前类组件中的全局setState()函数。

如果你需要定义多个states,可以多次调用useStatehook来实现。这个hook接收所有合法的JavaScript数据类型,比如说字符串、数字、布尔值、数组、对象等等。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
import React, { useState } from 'react'

function UserComponent(){
  const [name, setName] = useState('Jack')
  const [age, setAge] = useState(10)
  const [isLegal, setLegal] = useState(false)
  const [friends, setFriends] = useState(["John", "Luke"])
  
  return <h1>Hello World! My name is {name}</h1>
}

总的来说,useState给函数组件提供定义内部状态的能力。

useEffect hook

useEffect hook整合了componentDidMount, componentDidUpdate,componentWillUnmount这几个生命周函数的功能。它用于设定监听服务、从后端API获取数据、在组件被销毁前清除数据等操作。

我们可以先看看通常的功能在类组件中实现的方法:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
class Example extends React.Component {
  constructor(props){
    this.state = {
      name: 'Nacy',
    };
  }
  
  componentDidMount(){
    console.log(
    	`didMount triggered: Hello I'm ${this.state.name}`
    );
  }
  
  componentDidUpdate(){
    console.log(
    	`didUpdate triggered: Hello I'm ${this.state.name}`
    );
  }
  
  render(){
    return (
    	<div>
      	<p>{`Hello I'm ${this.state.name}`}</p>
        <button onClick={() => {this.setState({name: 'Niclo'})}}>
          Change me
        </button>
      </div>
    );
  }
}

我们之前也提过,componentDidMount只在组件被添加到DOM树的时候被执行一次,之后的渲染都不会执行到其中函数。为了每次渲染都执行,这时候就需要使用到componentDidUpdate函数。

使用useEffect hook相当于同时拥有了componentDidMountcomponentDidUpdate的特性,因为useEffect每次渲染都会被执行。它接受两个参数:

  • (必须)每次渲染执行的函数
  • (可选)一个包含需要监听状态改变的数组,当没有状态变量发生变化的时候,useEffect会跳过执行。

将上面的例子重写之后:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
const Example = props => {
  const [name, setName] = useState('Nacy');
  
  useEffect(()=>{
    console.log(`Hello I'm ${name}`);
  })
  
  return (
    <div>
      <p>{`Hello I'm ${this.state.name}`}</p>
      <button onClick={() => {setName('Niclo')}}>
        Change me
      </button>
    </div>
    );
}

需要注意的是useEffect中的函数会在每次渲染后被执行。这对于有些场景来说是对性能有影响的,我们可能只需要它在name变量被改变之后再运行,所以可以用到第二个可选参数:

1
2
3
useEffect(() => {
  console.log(`Hello I'm ${name}`);
}, [name]);

如果你有需要在组件移除DOM树之前执行的代码,实现类似componentWillUnmount功能,你需要在第一个参数传入的函数中通过 return 调用这些代码

1
2
3
4
5
  useEffect(()=>{
    console.log(`useEffect function`);
    
    return () => { console.log("componentWillUnmount effect"); }
  }, [name]);

为了实现类似componentDidMount功能。只执行一次代码,你可以向第二个参数传递一个空数组.

1
2
3
  useEffect(()=>{
    console.log(`useEffect function`);
  }, []);

空数组表明该effect没有任何需要监听改变的依赖,所以当它被挂载之后将不再执行。

rules of hooks

只在最顶层调用Hooks

不要在循环、条件语句以及内嵌方法中调用hooks。如果你需要根据条件使用hooks,将条件写到hooks函数中

低情商:

1
2
3
4
5
if (name !=== ''){
  useEffect(function logger(){
    console.log(name)
  });
}

高情商:

1
2
3
4
5
useEffect(function logger(){
  if (name !== ''){
    console.log(name)
  }
});

这条规则能够确保Hooks在每次组件被渲染时候都能按照顺序执行,当你有多个useState useEffect调用的时候,这尤为重要。

只在函数组件中调用

不要在纯JavaScript 函数中调用hooks,它应该只出现在函数组件以及自定义hooks中。遵守这条规则能够让组件逻辑保持清晰,便于理解。

根据条件渲染不同结果

你可以在JSX中根据条件来渲染不同的输出,比如说我们需要根据用户状态来显示登录和登出按钮:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
import React from "react"

function App(props){
  const {users} = props
  
  if (user){
      return (
        <>
          <h1>Hello there!</h1>
          <button>Login</button>
        </>
  		)
  }
  return (
      <>
        <h1>Hello there!</h1>
        <button>Logout</button>
      </>
  )
}

export default App

我们不需要使用else语句,因为React在遇到return会立刻返回,不再执行接下的代码。

善用变量来部分渲染

假如我们有这样一个需求:需要动态的渲染UI中的部分内容。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
import React from "react"

function App(props){
  const {user} = props
  
  let button = <button>Login</button>
      
  if (user){
    button = <button>Logout</button>
  }
  
  return (
    <>
    	<h1>Hello there!</h1>
      {button}
    </>
  )
}

export default App

区别与之前写两个返回表达式,这里你只需要将需要改变的部分存入变量,减少了静态UI部分的书写。

使用&&操作符

当有需求:只有特定需求满足才渲染否则返回null,比如说只有当用户有新邮件时候才渲染动态消息:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
import React from "react"

function App(props){
  const newEmails = 2
  
  return (
    <>
    	<h1>Hello there!</h1>
      {newEmails >0 && 
       <h2>
         You have {newEmails} new emails in your inbox.
       </h2>
      }
    </>
  )
}

export default App

使用三元表达式

同样的三元表达式经常用来条件渲染组件

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
import React from "react"

function App(props){
  const {user} = props
  
  return (
    <>
    	<h1>Hello there!</h1>
    	{ user? <button>Logout</button>:<button>Login</button> }
    </>
  )
}

export default App