React Function Components(React 的函數組件)

轉譯自:https://www.robinwieruch.de/react-function-component
本文在已發佈在GitHub(https://github.com/clxering/Technical-Articles-Collection/blob/master/React/React-Function-Components.md)歡迎糾錯訂正

React Function Components -- also known as React Functional Components -- are the status quo of writing modern React applications. In the past, there have been various React Component Types, but with the introduction of React Hooks it's possible to write your entire application with just functions as React components.

React Function Components(也稱爲 React 函數組件)是現代 React 應用程序的組成模塊。在過去,有多種 React 組件類型,但是隨着 React 鉤子 的引入,僅用 React 函數組件來編寫整個應用程序成爲可能。

This in-depth guide shows you everything about React Function Components -- which are basically just JavaScript Functions being React Components which return JSX (React's Syntax) -- so that after you have read this tutorial you should be well prepared to implement modern React applications with them.

這個深入的指南向你展示了 React 函數組件的所有內容(基於返回 JSX (React 語法)的 React 組件的 JavaScript 函數),因此,在閱讀本教程之後,你應該做好充分準備,用它們實現現代的 React 應用程序。

Note: There are several synonyms for this kind of component in React. You may have seen different variations such as "React Function only Component" or "React Component as Function".

注意:React 中有幾種關於此類組件的同義詞。你可能見過不同的變化,如 React Function only Component 或 React Component as Function

Table of Contents(目錄列表)

React Function Component Example(函數組件的例子)

Let's start with a simple example of a Functional Component in React defined as App which returns JSX:

讓我們從一個簡單例子開始,它定義了一個 App 函數組件,並返回 JSX:

import React from 'react';

function App() {
  const greeting = 'Hello Function Component!';

  return <h1>{greeting}</h1>;
}

export default App;

That's already the essential React Function Component Syntax. The definition of the component happens with just a JavaScript Function which has to return JSX -- React's syntax for defining a mix of HTML and JavaScript whereas the JavaScript is used with curly braces within the HTML. In our case, we render a variable called greeting, which is defined in the component's function body, and is returned as HTML headline in JSX.

這已經是基本的 React 函數組件語法了。組件的定義只需要一個 JavaScript 函數,該函數必須返回 JSX(React 用於定義混合 HTML 和 JavaScript 的語法,而 JavaScript 在 HTML 中使用花括號)。在例子中,我們渲染了一個名爲 greeting 的變量,它在組件的函數體中定義,並在 JSX 中作爲 HTML 標題返回。

Note: If you are familiar with React Class Components, you may have noticed that a Functional Component is a React Component without render function. Everything defined in the function's body is the render function which returns JSX in the end.

注意:如果你熟悉 React 類組件,你可能已經注意到函數組件是沒有 render 函數的 React 組件。在函數體中定義的所有內容都是 render 函數,該函數最後返回 JSX。

Now, if you want to render a React Component inside a Function Component, you define another component and render it as HTML element with JSX within the other component's body:

如果你想在函數組件中渲染一個 React 組件,你需要定義一個新組件並將其渲染爲 HTML 元素,而 JSX 則在新組件的主體中:

import React from 'react';

function App() {
  return <Headline />;
}

function Headline() {
  const greeting = 'Hello Function Component!';

  return <h1>{greeting}</h1>;
}

export default App;

Basically you have a function as Child Component now. Defining React Components and rendering them within each other makes Composition in React possible. You can decide where to render a component and how to render it.

你現在有了一個函數作爲子組件。定義 React 組件並互相渲染,使得 在 React 中組合 成爲可能。你可以決定在何處渲染組件以及如何渲染它。

React Function Component: props(函數組件的 props)

Let's learn about a React Function Component with props. In React, props are used to pass information from component to component. If you don't know about props in React, cross-read the linked article. Essentially props in React are always passed down the component tree:

讓我們學習一下 React 函數組件的 props。在 React 中,props 用於在組件之間傳遞信息。如果你不知道 React 裏的 props,可交叉閱讀相關文章。本質上,React 中的 props 總是沿着組件樹向下傳遞:

import React from 'react';

function App() {
  const greeting = 'Hello Function Component!';

  return <Headline value={greeting} />;
}

function Headline(props) {
  return <h1>{props.value}</h1>;
}

export default App;

Props are the React Function Component's parameters. Whereas the component can stay generic, we decide from the outside what it should render (or how it should behave). When rendering a component (e.g. Headline in App component), you can pass props as HTML attributes to the component. Then in the Function Component the props object is available as argument in the function signature.

props 是 React 函數組件的參數。雖然組件可以保持通用,但是我們從外部決定它應該渲染什麼(或者它應該有什麼行爲)。當渲染一個組件時(例如在 App 組件中的 Headline),你可以將 props 作爲 HTML 屬性傳遞給組件。在函數組件中,props 對象在函數簽名中是可用的參數。

Since props are always coming as object, and most often you need to extract the information from the props anyway, JavaScript object destructuring comes in handy. You can directly use it in the function signature for the props object:

由於 props 總是以對象形式出現,而且大多數情況下無論如何都需要從 props 中提取信息,因此 JavaScript 對象解構 就派上用場了。你可以直接在 props 對象的函數簽名中使用:

import React from 'react';

function App() {
  const greeting = 'Hello Function Component!';

  return <Headline value={greeting} />;
}

function Headline({ value }) {
  return <h1>{value}</h1>;
}

export default App;

Note: If you forget the JavaScript destructuring and just access props from the component's function signature like function Headline(value1, value2) { ... }, you may see a "props undefined"-messages. It doesn't work this way, because props are always accessible as first argument of the function and can be destructured from there: function Headline({ value1, value2 }) { ... }.

注意:如果你忘記了 JavaScript 的解構,而只是從組件的函數簽名,如 function Headline(value1, value2) { ... } 訪問 props,你可能會看到一個 "props undefined" 的消息。它不是這樣工作的,因爲 props 總是作爲函數的第一個參數,可以從函數簽名那裏解構:function Headline({ value1, value2 }) { ... }

If you want to learn more tricks and tips about React props, again check out the linked article from the beginning of this section. There you will learn about cases where you don't want to destructure your props and simply pass them to the next child component with the ...syntax known as spread operator.

如果你想了解更多關於 React props 的技巧和提示,請再次查看本節開頭的鏈接文章。在文章中,你將瞭解不對 props 解構的情況,並簡單地用帶有 的語法將它們傳遞給下一個子組件,這種語法稱爲擴展運算符。

React Arrow Function Component(React 的箭頭函數組件)

With the introduction of JavaScript ES6, new coding concepts were introduced to JavaScript and therefore to React. For instance, a JavaScript function can be expressed as lambda (arrow function). That's why a Function Component is sometimes called Arrow Function Components (or maybe also Lambda Function Component). Let's see our refactored React Component with an Arrow Function:

隨着 JavaScript ES6 的引入,新的編碼概念被引入到 JavaScript 中,React 也不例外。例如,一個 JavaScript 函數可以表示爲 lambda 箭頭函數。這就是爲什麼一個函數組件有時被稱爲箭頭函數組件(或者 Lambda 函數組件)。讓我們看看用箭頭函數重構的 React 組件:

import React from 'react';

const App = () => {
  const greeting = 'Hello Function Component!';

  return <Headline value={greeting} />;
};

const Headline = ({ value }) => {
  return <h1>{value}</h1>;
};

export default App;

Both React Arrow Function Components use a function block body now. However, the second component can be made more lightweight with a concise body for the function, because it only returns the output of the component without doing something else in between. When leaving away the curly braces, the explicit return becomes an implicit return and can be left out as well:

兩個 React 箭頭函數組件現在都使用一個函數塊體。但是,第二個組件可以用一個簡潔的函數體使其更輕量級,因爲它只返回組件的輸出,而不做其他事情。當去掉花括號時,顯式 return 變成隱式 return,也可以省略:

import React from 'react';

const App = () => {
  const greeting = 'Hello Function Component!';

  return <Headline value={greeting} />;
};

const Headline = ({ value }) =>
  <h1>{value}</h1>;

export default App;

When using arrow functions for React components, nothing changes for the props. They are still accessible as arguments as before. It's a React Function Component with ES6 Functions expressed as arrows instead of ES5 Functions which are the more default way of expressing functions in JS.

當 React 組件使用箭頭函數時,props 不會發生任何變化。它們仍然可以作爲參數使用。它是一個用箭頭表示 ES6 函數的 React 函數組件,而不是在 JS 中表示函數的默認方式 ES5 函數。

Note: If you run into a "React Component Arrow Function Unexpected Token" error, make sure that JavaScript ES6 is available for your React application. Normally when using create-react-app this should be given, otherwise, if you set up the project yourself, Babel is enabling ES6 and beyond features for your React application.

注意:如果遇到 React Component Arrow Function Unexpected Token 錯誤,請確保 React 應用程序可用 JavaScript ES6。通常情況下,當使用 create-react-app 時,應該確認這個選項,否則,如果你自己設置項目,Babel 將爲你的 React 應用程序啓用 ES6 和更多功能。

React Stateless Function Component(React 的無狀態函數組件)

Every component we have seen so far can be called Stateless Function Component. They just receive an input as props and return an output as JSX: (props) => JSX. The input, only if available in form of props, shapes the rendered output. These kind of components don't manage state and don't have any side-effects (e.g. accessing the browser's local storage). People call them Functional Stateless Components, because they are stateless and expressed by a function. However, React Hooks made it possible to have state in Function Components.

到目前爲止,我們看到的每個組件都可以稱爲無狀態函數組件。它們只是接收一個 props 的輸入,並返回一個 JSX:(props) => JSX 作爲輸出。輸入只能以 props 的形式提供給輸出渲染。這些組件不管理狀態,也沒有任何副作用(例如訪問瀏覽器的本地存儲)。人們稱它們爲無狀態函數組件,因爲它們是無狀態的,並由函數表示。但是,React 鉤子使得函數組件中有狀態成爲可能。

React Function Component: state(React 函數組件之:狀態)

React Hooks made it possible to use state (and side-effects) in Function Components. Finally we can create a React Function Component with state! Let's say we moved all logic to our other Function Component and don't pass any props to it:

React 鉤子使得在函數組件中使用狀態(和副作用)成爲可能。最後,我們可以創建一個有狀態的 React 函數組件!我們把所有的邏輯都移到了另一個函數組件上,並且沒有給它傳遞任何 props:

import React from 'react';

const App = () => {
  return <Headline />;
};

const Headline = () => {
  const greeting = 'Hello Function Component!';

  return <h1>{greeting}</h1>;
};

export default App;

So far, a user of this application has no way of interacting with the application and thus no way of changing the greeting variable. The application is static and not interactive at all. State is what makes React components interactive; and exciting as well. A React Hook helps us to accomplish it:

到目前爲止,此應用程序的用戶還無法與應用程序交互,因此無法更改 greeting 變量。應用程序是靜態的,完全沒有交互性。狀態使 React 組件具有交互性;也很令人興奮。React Hook 幫助我們完成它:

import React, { useState } from 'react';

const App = () => {
  return <Headline />;
};

const Headline = () => {
  const [greeting, setGreeting] = useState(
    'Hello Function Component!'
  );

  return <h1>{greeting}</h1>;
};

export default App;

The useState hook takes an initial state as parameter and returns an array which holds the current state as first item and a function to change the state as second item. We are using JavaScript array destructuring to access both items with a shorthand expression. In addition, the destructuring let's us name the variables ourselves.

useState 鉤子將初始狀態作爲參數,並返回一個數組,其中當前狀態是第一項,更改狀態的函數是第二項。我們正在使用 JavaScript 數組解構 來訪問這兩個項與一個縮寫表達式。另外,解構還可以讓我們自己給變量命名。

Let's add an input field to change the state with the setGreeting() function:

讓我們添加一個 input field 來改變 setGreeting() 函數的狀態:

import React, { useState } from 'react';

const App = () => {
  return <Headline />;
};

const Headline = () => {
  const [greeting, setGreeting] = useState(
    'Hello Function Component!'
  );

  return (
    <div>
      <h1>{greeting}</h1>

      <input
        type="text"
        value={greeting}
        onChange={event => setGreeting(event.target.value)}
      />
    </div>
  );
};

export default App;

By providing an event handler to the input field, we are able to do something with a callback function when the input field changes its value. As argument of the callback function we receive a synthetic React event which holds the current value of the input field. This value is ultimately used to set the new state for the Function Component with an inline arrow function. We will see later how to extract this function from there.

通過向 input field 提供事件處理程序,我們可以在 input field 更改其值時使用回調函數來完成某些事情。作爲回調函數的參數,我們接收一個保存 input field 當前值的 synthetic React 事件。此值最終用於設置帶有內聯箭頭函數的函數組件的新狀態。稍後我們將看到如何從那裏提取這個函數。

Note: The input field receives the value of the component state too, because you want to control the state (value) of the input field and don't let the native HTML element's internal state take over. Doing it this way, the component has become a controlled component.

注意:input field 也接收組件狀態的值,因爲你希望控制輸入字段的狀態(值),而不讓本機 HTML 元素的內部狀態接管。這樣做,組件就變成了受控組件。

As you have seen, React Hooks enable us to use state in React (Arrow) Function Components. Whereas you would have used a setState method to write state in a Class Component, you can use the useState hook to write state in a Function Component.

如你所見,React 鉤子使我們能夠在 React(箭頭)函數組件中使用狀態。雖然可以使用 setState 方法在類組件中寫入狀態,但是可以使用 useState 鉤子在函數組件中寫入狀態。

Note: If you want to use React's Context in Function Components, check out React's Context Hook called useContext for reading from React's Context in a component.

注意:如果你想在函數組件中使用 React 的上下文,請查看 React 的上下文鉤子 useContext,以便從組件中的 React 上下文中讀取內容。

React Function Component: Event Handler(React 函數組件之:事件處理程序)

In the previous example you have used an onChange event handler for the input field. That's appropriate, because you want to be notified every time the internal value of the input field has changed. In the case of other HTML form elements, you have several other React event handlers at your disposal such as onClick, onMouseDown, and onBlur.

在前面的示例中,你已經爲 input field 使用了 onChange 事件處理程序。這是恰當的,因爲你希望在每次 input field 的內部值發生更改時都得到通知。對於其他 HTML 表單元素,你可以使用其他幾個 React 事件處理程序,如 onClick、onMouseDown 和 onBlur。

Note: The onChange event handler is only one of the handlers for HTML form elements. For instance, a button would offer an onClick event handler to react on click events.

注意:onChange 事件處理程序只是 HTML 表單元素的處理程序之一。例如,按鈕將提供一個 onClick 事件處理程序來對單擊事件作出反應。

So far, we have used an arrow function to inline the event handler for out input field. What about extracting it as standalone function inside the component? It would become a named function then:

到目前爲止,我們已經使用了一個箭頭函數來內聯 input field 的事件處理程序,並影響輸出。將它作爲獨立的函數提取到組件中呢?它將成爲一個具名函數:

import React, { useState } from 'react';

const App = () => {
  return <Headline />;
};

const Headline = () => {
  const [greeting, setGreeting] = useState(
    'Hello Function Component!'
  );

  const handleChange = event => setGreeting(event.target.value);

  return (
    <div>
      <h1>{greeting}</h1>

      <input type="text" value={greeting} onChange={handleChange} />
    </div>
  );
};

export default App;

We have used an arrow function to define the function within the component. If you have used class methods in React Class Components before, this way of defining functions inside a React Function Component is the equivalent. You could call it the "React Function Component Methods"-equivalent to class components. You can create or add as many functions inside the Functional Component as you want to act as explicit event handlers or to encapsulate other business logic.

我們使用了一個箭頭函數來定義組件中的函數。如果你以前在 React 類組件中使用過類方法,那麼在 React 函數組件中定義函數的這種方法是等效的。你可以稱之爲「React 函數組件方法」(相當於類組件)。你可以在函數組件中創建或添加儘可能多的函數,以充當顯式事件處理程序或封裝其他業務邏輯。

React Function Component: Callback Function(React 函數組件之:回調函數)

Everything happens in our Child Function Component. There are no props passed to it, even though you have seen before how a string variable for the greeting can be passed from the Parent Component to the Child Component. Is it possible to pass a function to a component as prop as well? Somehow it must be possible to call a component function from the outside! Let's see how this works:

一切都在我們的子函數組件中發生。沒有向它傳遞任何 props,即使你以前已經看到了如何將用於 greeting 的字符串變量從父組件傳遞到子組件。有可能將一個函數作爲 props 傳遞給一個組件嗎?無論如何,必須能夠從外部調用組件函數!讓我們看看它是如何工作的:

import React, { useState } from 'react';

const App = () => {
  const [greeting, setGreeting] = useState(
    'Hello Function Component!'
  );

  const handleChange = event => setGreeting(event.target.value);

  return (
    <Headline headline={greeting} onChangeHeadline={handleChange} />
  );
};

const Headline = ({ headline, onChangeHeadline }) => (
  <div>
    <h1>{headline}</h1>

    <input type="text" value={headline} onChange={onChangeHeadline} />
  </div>
);

export default App;

That's all to it. You can pass a function to a Child Component and handle what happens up in the Parent Component. You could also execute something in between in the Child Component (Headline component) for the onChangeHeadline function -- like trimming the value -- to add extra functionality inside the Child Component. That's how you would be able to call a Child Component's function from a Parent Component.

僅此而已。可以將函數傳遞給子組件並處理父組件中發生的事情。你還可以在子組件(Headline 組件)中爲 onChangeHeadline 函數執行一些操作(比如調整值),以在子組件中添加額外的功能。這就是如何從父組件調用子組件的函數。

Let's take this example one step further by introducing a Sibling Component for the Headline component. It could be an abstract Input component:

讓我們通過爲 Headline 組件引入一個 Sibling 組件來進一步研究這個示例。它可以是一個抽象的 Input 組件:

import React, { useState } from 'react';

const App = () => {
  const [greeting, setGreeting] = useState(
    'Hello Function Component!'
  );

  const handleChange = event => setGreeting(event.target.value);

  return (
    <div>
      <Headline headline={greeting} />

      <Input value={greeting} onChangeInput={handleChange}>
        Set Greeting:
      </Input>
    </div>
  );
};

const Headline = ({ headline }) => <h1>{headline}</h1>;

const Input = ({ value, onChangeInput, children }) => (
  <label>
    {children}
    <input type="text" value={value} onChange={onChangeInput} />
  </label>
);

export default App;

I find this is a perfect yet minimal example to illustrate how to pass functions between components as props; and more importantly how to share a function between components. You have one Parent Component which manages the logic and two Child Components -- which are siblings -- that receive props. These props can always include a callback function to call a function in another component. Basically that's how it's possible to call a function in different components in React.

我發現這是一個完美而又簡單的例子,它演示瞭如何在組件之間傳遞函數;更重要的是如何在組件之間共享一個函數。你有一個管理邏輯的父組件和兩個接收 props 的子組件(它們是兄弟組件)。這些支持總是可以包含一個回調函數來調用另一個組件中的函數。基本上這就是在 React 中調用不同組件中的函數的方法。

Override Component Function with React(覆蓋 React 組件)

It's shouldn't happen often, but I have heard people asking me this question. How would you override a component's function? You need to take the same approach as for overriding any other passed prop to a component by giving it a default value:

這種情況不應該經常發生,但是有人問我這個問題。如何覆蓋組件的函數?你需要採取相同的方法覆蓋任何其他通過的 props 組件給它一個默認值:

import React from 'react';

const App = () => {
  const sayHello = () => console.log('Hello');

  return <Button handleClick={sayHello} />;
};

const Button = ({ handleClick }) => {
  const sayDefault = () => console.log('Default');

  const onClick = handleClick || sayDefault;

  return (
    <button type="button" onClick={onClick}>
      Button
    </button>
  );
};

export default App;

You can assign the default value in the function signature for the destructuring as well:

你也可以在函數簽名中爲解構指定默認值:

import React from 'react';

const App = () => {
  const sayHello = () => console.log('Hello');

  return <Button handleClick={sayHello} />;
};

const Button = ({ handleClick = () => console.log('Default') }) => (
  <button type="button" onClick={handleClick}>
    Button
  </button>
);

export default App;

You can also give a React Function Component default props -- which is another alternative:

你也可以給一個 React 函數組件默認的 props(這是另一種選擇):

import React from 'react';

const App = () => {
  const sayHello = () => console.log('Hello');

  return <Button handleClick={sayHello} />;
};

const Button = ({ handleClick }) => (
  <button type="button" onClick={handleClick}>
    Button
  </button>
);

Button.defaultProps = {
  handleClick: () => console.log('Default'),
};

export default App;

All of these approaches can be used to define default props (in this case a default function), to be able to override it later from the outside by passing an explicit prop (e.g. function) to the component.

所有這些方法都可以用來定義默認的 props(在本例中是一個默認的函數),以便以後通過向組件傳遞一個顯式的 props(例如函數)來從外部覆蓋它。

Async Function in Component with React(在 React 組件的異步函數)

Another special case may be an async function in a React component. But there is nothing special about it, because it doesn't matter if the function is asynchronously executed or not:

另一種特殊情況可能是 React 組件中的異步函數。但是它沒有什麼特別之處,因爲它與函數是否異步執行無關:

import React from 'react';

const App = () => {
  const sayHello = () =>
    setTimeout(() => console.log('Hello'), 1000);

  return <Button handleClick={sayHello} />;
};

const Button = ({ handleClick }) => (
  <button type="button" onClick={handleClick}>
    Button
  </button>
);

export default App;

The function executes delayed without any further instructions from your side within the component. The component will also rerender asynchronously in case props or state have changed. Take the following code as example to see how we set state with a artificial delay by using setTimeout:

該函數延遲執行,沒有來自組件內部的任何進一步指令。如果 props 或狀態發生了更改,組件還將異步地重新運行。以下面的代碼爲例,看看我們如何使用 setTimeout 設置一個人爲的延遲狀態:

import React, { useState } from 'react';

const App = () => {
  const [count, setCount] = useState(0);

  const handleIncrement = () =>
    setTimeout(
      () => setCount(currentCount => currentCount + 1),
      1000
    );

  const handleDecrement = () =>
    setTimeout(
      () => setCount(currentCount => currentCount - 1),
      1000
    );

  return (
    <div>
      <h1>{count}</h1>

      <Button handleClick={handleIncrement}>Increment</Button>
      <Button handleClick={handleDecrement}>Decrement</Button>
    </div>
  );
};

const Button = ({ handleClick, children }) => (
  <button type="button" onClick={handleClick}>
    {children}
  </button>
);

export default App;

Also note that we are using a callback function within the setCount state function to access the current state. Since setter functions from useState are executed asynchronously by nature, you want to make sure to perform your state change on the current state and not on any stale state.

還要注意,我們在 setCount 狀態函數中使用了一個回調函數來訪問當前狀態。由於 useState 中的 setter 函數本質上是異步執行的,因此你需要確保對當前狀態執行狀態更改,而不是對任何陳舊狀態執行狀態更改。

Experiment: If you wouldn't use the callback function within the State Hook, but rather act upon the count variable directly (e.g. setCount(count + 1)), you wouldn't be able to increase the value from 0 to 2 with a quick double click, because both times the function would be executed on a count state of 0.

實驗:如果你不使用 State Hook 中的回調函數,而是直接作用於 count 變量(例如 setCount(count + 1)),你無法通過快速雙擊將值從 0 增加到 2,因爲兩次都將在 count 狀態爲 0 時執行函數。

Read more about how to fetch data with Function Components with React Hooks.

閱讀更多:如何使用 React 鉤子的函數組件獲取數據

React Function Component: Lifecycle(React 函數組件之生命週期)

If you have used React Class Components before, you may be used to lifecycle methods such as componentDidMount, componentWillUnmount and shouldComponentUpdate. You don't have these in Function Components, so let's see how you can implement them instead.

如果你以前使用過 React 類組件,那麼你可能已經熟悉了諸如 componentDidMount、componentWillUnmount 和 shouldComponentUpdate 之類的生命週期方法。在函數組件中沒有這些,所以讓我們看看如何實現它們。

First of all, you have no constructor in a Function Component. Usually the constructor would have been used in a React Class Component to allocate initial state. As you have seen, you don't need it in a Function Component, because you allocate initial state with the useState hook and set up functions within the Function Component for further business logic:

首先,函數組件中沒有構造函數。通常在 React 類組件中使用構造函數來分配初始狀態。正如你所看到的,在函數組件中不需要它,是因爲使用 useState 鉤子分配初始狀態,並在函數組件中爲進一步的業務邏輯設置函數:

import React, { useState } from 'react';

const App = () => {
  const [count, setCount] = useState(0);

  const handleIncrement = () =>
    setCount(currentCount => currentCount + 1);

  const handleDecrement = () =>
    setCount(currentCount => currentCount - 1);

  return (
    <div>
      <h1>{count}</h1>

      <button type="button" onClick={handleIncrement}>
        Increment
      </button>
      <button type="button" onClick={handleDecrement}>
        Decrement
      </button>
    </div>
  );
};

export default App;

React Functional Component: Mount(React 函數組件之掛載)

Second, there is the mounting lifecycle for React components when they are rendered for the first time. If you want to execute something when a React Function Component did mount, you can use the useEffect hook:

其次,在首次渲染 React 組件時,有一個掛載生命週期。如果你想在一個 React 函數組件 掛載 時做些事情,你可以使用 useEffect 鉤子:

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

const App = () => {
  const [count, setCount] = useState(0);

  const handleIncrement = () =>
    setCount(currentCount => currentCount + 1);

  const handleDecrement = () =>
    setCount(currentCount => currentCount - 1);

  useEffect(() => setCount(currentCount => currentCount + 1), []);

  return (
    <div>
      <h1>{count}</h1>

      <button type="button" onClick={handleIncrement}>
        Increment
      </button>
      <button type="button" onClick={handleDecrement}>
        Decrement
      </button>
    </div>
  );
};

export default App;

If you try out this example, you will see the count 0 and 1 shortly displayed after each other. The first render of the component shows the count of 0 from the initial state -- whereas after the component did mount actually, the Effect Hook will run to set a new count state of 1.

如果你嘗試使用這個示例,你將看到 0 和 1 分別顯示在一起。組件的第一次渲染顯示了從初始狀態開始的 0 count(然而,在組件實際掛載之後,Effect Hook 將運行以設置新的 count 狀態爲1)。

It's important to note the empty array as second argument for the Effect Hook which makes sure to trigger the effect only on component load (mount) and component unload (unmount).

一定要注意,空數組是 Effect 鉤子的第二個參數,它確保只在組件加載(mount)和組件卸載(unmount)時觸發效果。

Experiment: If you would leave the second argument of the Effect Hook empty, you would run into an infinite loop of increasing the count by 1, because the Effect Hook always runs after state has changed. Since the Effect Hook triggers another state change, it will run again and again to increase the count.

實驗:如果你讓 Effect 鉤子的第二個參數爲空,你會進入一個無限循環,計數增加 1,因爲 Effect 鉤子總是在狀態改變後運行。因爲 Effect Hook 會觸發另一個狀態改變,所以它會一次又一次的運行來增加計數。

React Functional Component: Update(React 函數組件之:更新)

Every time incoming props or state of the component change, the component triggers a rerender to display the latest status quo which is often derived from the props and state. A render executes everything within the Function Component's body.

每當輸入的 props 或組件的狀態更改時,組件將觸發一個重新運行程序來顯示最新的狀態,這些狀態通常來自 props 和狀態。渲染執行函數組件主體內的所有內容。

Note: In case a Function Component is not updating properly in your application, it's always a good first debugging attempt to console log state and props of the component. If both don't change, there is no new render executed, and hence you don't see a console log of the output in the first place.

注意:如果在你的應用程序中某個函數組件沒有正確地更新,那麼首先嚐試調試以控制該組件的日誌狀態和 props 總是好的。如果兩者都沒有改變,就不會執行新的渲染,因此你不會首先看到輸出的控制檯日誌。

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

const App = () => {
  console.log('Does it render?');

  const [count, setCount] = useState(0);

  console.log(`My count is ${count}!`);

  const handleIncrement = () =>
    setCount(currentCount => currentCount + 1);

  const handleDecrement = () =>
    setCount(currentCount => currentCount - 1);

  return (
    <div>
      <h1>{count}</h1>

      <button type="button" onClick={handleIncrement}>
        Increment
      </button>
      <button type="button" onClick={handleDecrement}>
        Decrement
      </button>
    </div>
  );
};

export default App;

If you want to act upon a rerender, you can use the Effect Hook again to do something after the component did update:

如果你想在渲染時動作,你可以使用 Effect 鉤子在組件更新後再次做一些事情:

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

const App = () => {
  const initialCount = +localStorage.getItem('storageCount') || 0;
  const [count, setCount] = useState(initialCount);

  const handleIncrement = () =>
    setCount(currentCount => currentCount + 1);

  const handleDecrement = () =>
    setCount(currentCount => currentCount - 1);

  useEffect(() => localStorage.setItem('storageCount', count));

  return (
    <div>
      <h1>{count}</h1>

      <button type="button" onClick={handleIncrement}>
        Increment
      </button>
      <button type="button" onClick={handleDecrement}>
        Decrement
      </button>
    </div>
  );
};

export default App;

Now every time the Function Component rerenders, the count is stored into the browser's local storage. Every time you fresh the browser page, the count from the browser's local storage, in case there is a count in the storage, is set as initial state.

現在,每次函數組件重新渲染時,count 都存儲在瀏覽器的本地存儲中。每次刷新瀏覽器頁面時,來自瀏覽器本地存儲的 count(如果存儲中有)都被設置爲初始狀態。

You can also specify when the Effect Hook should run depending on the variables you pass into the array as second argument. Then every time one of the variables change, the Effect Hook runs. In this case it makes sense to store the count only if the count has changed:

還可以根據傳遞給數組的第二個參數變量來確定何時運行 Effect Hook。然後,每當變量發生變化時,Effect Hook 就會運行。在這種情況下,只有在 count 發生變化時纔有必要存儲計數:

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

const App = () => {
  const initialCount = +localStorage.getItem('storageCount') || 0;
  const [count, setCount] = useState(initialCount);

  const handleIncrement = () =>
    setCount(currentCount => currentCount + 1);

  const handleDecrement = () =>
    setCount(currentCount => currentCount - 1);

  useEffect(() => localStorage.setItem('storageCount', count), [
    count,
  ]);

  return (
    <div>
      <h1>{count}</h1>

      <button type="button" onClick={handleIncrement}>
        Increment
      </button>
      <button type="button" onClick={handleDecrement}>
        Decrement
      </button>
    </div>
  );
};

export default App;

By using the second argument of the Effect Hook with care, you can decide whether it runs:

小心使用 Effect Hook 的第二個參數,你可以決定它是否運行:

  • every time (no argument)

每一次(無參數)

  • only on mount and unmount ([] argument)

只在掛載和非掛載時([] 參數)

  • only when a certain variable changes (e.g. [count] argument)

只有當某個變量改變時(例如 [count]

Note: A React Function Component force update can be done by using this neat trick. However, you should be careful when applying this pattern, because maybe you can solve the problem a different way.

使用這個 精妙的技巧 可以完成一個 React 函數組件的強制更新。但是,在應用此模式時應該小心,畢竟還可以用其他途徑解決問題。

Pure React Function Component(純 React 函數組件)

React Class Components offered the possibility to decide whether a component has to rerender or not. It was achieved by using the PureComponent or shouldComponentUpdate to avoid performance bottlenecks in React by preventing rerenders. Let's take the following extended example:

React 類組件提供了一種可能性,可以決定組件是否必須重新渲染。它是通過使用 PureComponent 或 shouldComponentUpdate 來避免 React 中的性能瓶頸,從而防止渲染。讓我們舉一個擴展的例子:

import React, { useState } from 'react';

const App = () => {
  const [greeting, setGreeting] = useState('Hello React!');
  const [count, setCount] = useState(0);

  const handleIncrement = () =>
    setCount(currentCount => currentCount + 1);

  const handleDecrement = () =>
    setCount(currentCount => currentCount - 1);

  const handleChange = event => setGreeting(event.target.value);

  return (
    <div>
      <input type="text" onChange={handleChange} />

      <Count count={count} />

      <button type="button" onClick={handleIncrement}>
        Increment
      </button>
      <button type="button" onClick={handleDecrement}>
        Decrement
      </button>
    </div>
  );
};

const Count = ({ count }) => {
  console.log('Does it (re)render?');

  return <h1>{count}</h1>;
};

export default App;

In this case, every time you type something in the input field, the App component updates its state, rerenders, and rerenders the Count component as well. React memo -- which is one of React's top level APIs -- can be used for React Function Components to prevent a rerender when the incoming props of this component haven't changed:

在這種情況下,每當你在 input field 中鍵入一些內容時,App 組件都會更新其狀態、重新渲染以及重新渲染 Count 組件。React memo(React 的頂級 API 之一)可用於 React 函數組件,以防止在該組件的傳入 props 未更改時重新渲染:

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

const App = () => {
  const [greeting, setGreeting] = useState('Hello React!');
  const [count, setCount] = useState(0);

  const handleIncrement = () =>
    setCount(currentCount => currentCount + 1);

  const handleDecrement = () =>
    setCount(currentCount => currentCount - 1);

  const handleChange = event => setGreeting(event.target.value);

  return (
    <div>
      <input type="text" onChange={handleChange} />

      <Count count={count} />

      <button type="button" onClick={handleIncrement}>
        Increment
      </button>
      <button type="button" onClick={handleDecrement}>
        Decrement
      </button>
    </div>
  );
};

const Count = memo(({ count }) => {
  console.log('Does it (re)render?');

  return <h1>{count}</h1>;
});

export default App;

Now, the Count component doesn't update anymore when the user types something into the input field. Only the App component rerenders. This performance optimization shouldn't be used as default though. I would recommend to check it out when you run into issues when the rerendering of components takes too long (e.g. rendering and updating a large list of items in a Table component).

現在,當用戶在 input field 中輸入內容時,Count 組件不再更新。只有 App 組件重新渲染。不過,這種性能優化不應該當爲默認行爲。我的建議是,在重新渲染組件的時間太長(例如,在一個 Table 組件中渲染和更新一個很大的項目列表)而遇到問題時再進行。

React Function Component: Export and Import(React 函數組件之:Export 和 Import)

Eventually you will separate components into their own files. Since React Components are functions (or classes), you can use the standard import and export statements provided by JavaScript. For instance, you can define and export a component in one file:

最終你會將組件各自分離到獨立的文件中。因爲 React 組件是函數(或類),所以可以使用 JavaScript 提供的標準 importexport 語句。例如,你可以在一個文件中定義和導出一個組件:

// src/components/Headline.js

import React from 'react';

const Headline = (props) => {
  return <h1>{props.value}</h1>;
};

export default Headline;

And import it in another file:

在另一個文件中將其導入:

// src/components/App.js

import React from 'react';

import Headline from './Headline.js';

const App = () => {
  const greeting = 'Hello Function Component!';

  return <Headline value={greeting} />;
};

export default App;

Note: If a Function Component is not defined, console log your exports and imports to get a better understanding of where you made a mistake. Maybe you used a named export and expected it to be a default export.

注意:如果沒有定義函數組件,控制檯將記錄你的導出和導入,以便更好地瞭解在哪裏犯了錯誤。可能使用了一個具名導出,並錯誤認爲它是一個默認導出。

If you don't care about the component name by defining the variable, you can keep it as Anonymous Function Component when using a default export on the Function Component:

如果你不介意組件名稱由變量定義,你可以讓它作爲匿名函數組件,此時使用默認導出函數組件:

import React from 'react';

import Headline from './Headline.js';

export default () => {
  const greeting = 'Hello Function Component!';

  return <Headline value={greeting} />;
};

However, when doing it this way, React Dev Tools cannot identify the component because it has no display name. You may see an Unknown Component in your browser's developer tools.

但是,當這樣做時,React Dev 工具無法識別組件,因爲它沒有顯示名稱。可能會在瀏覽器的開發工具中看到一個未知的組件。

React Function Component: ref(React 函數組件之:ref)

A React Ref should only be used in rare cases such as accessing/manipulating the DOM manually (e.g. focus element), animations, and integrating third-party DOM libraries (e.g. D3). If you have to use a Ref in a Function Component, you can define it within the component. In the following case, the input field will get focused after the component did mount:

React Ref 應該只在極少數情況下使用,比如手動訪問或操作 DOM(比如 focus 元素)、動畫以及集成第三方 DOM 庫(比如 D3)。如果必須在函數組件中使用 Ref,則可以在組件中定義它。在以下情況下,組件掛載完成後,input field 將被聚焦:

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

const App = () => {
  const [greeting, setGreeting] = useState('Hello React!');

  const handleChange = event => setGreeting(event.target.value);

  return (
    <div>
      <h1>{greeting}</h1>

      <Input value={greeting} handleChange={handleChange} />
    </div>
  );
};

const Input = ({ value, handleChange }) => {
  const ref = useRef();

  useEffect(() => ref.current.focus(), []);

  return (
    <input
      type="text"
      value={value}
      onChange={handleChange}
      ref={ref}
    />
  );
};

export default App;

However, React Function Components cannot be given refs! If you try the following, the ref will be assigned to the component instance but not to the actual DOM node.

但是,React 函數組件不能提供 ref!如果你嘗試以下操作,ref 被分配給組件實例,而不是實際的 DOM 節點。

// Doesn't work!

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

const App = () => {
  const [greeting, setGreeting] = useState('Hello React!');

  const handleChange = event => setGreeting(event.target.value);

  const ref = useRef();

  useEffect(() => ref.current.focus(), []);

  return (
    <div>
      <h1>{greeting}</h1>

      <Input value={greeting} handleChange={handleChange} ref={ref} />
    </div>
  );
};

const Input = ({ value, handleChange, ref }) => (
  <input
    type="text"
    value={value}
    onChange={handleChange}
    ref={ref}
  />
);

export default App;

It's not recommended to pass a ref from a Parent Component to a Child Component and that's why the assumption has always been: React Function Components cannot have refs. However, if you need to pass a ref to a Function Component -- because you have to measure the size of a function component's DOM node, for example, or like in this case to focus an input field from the outside -- you can forward the ref:

不建議將 ref 從父組件傳遞給子組件,這就是爲什麼我們總是假設:React 函數組件不能有 ref。但是,如果你需要將一個 ref 傳遞給一個函數組件(例如,因爲你必須測量函數組件的 DOM 節點的大小,或者像本例中那樣從外部聚焦一個 input field),那麼你可以 轉發這個 ref

// Does work!

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

const App = () => {
  const [greeting, setGreeting] = useState('Hello React!');

  const handleChange = event => setGreeting(event.target.value);

  const ref = useRef();

  useEffect(() => ref.current.focus(), []);

  return (
    <div>
      <h1>{greeting}</h1>

      <Input value={greeting} handleChange={handleChange} ref={ref} />
    </div>
  );
};

const Input = forwardRef(({ value, handleChange }, ref) => (
  <input
    type="text"
    value={value}
    onChange={handleChange}
    ref={ref}
  />
));

export default App;

There are a few other things you may want to know about React Refs, so check out this article: How to use Ref in React or the official React documentation.

關於 React Ref,你可能還想了解其他一些事情,所以請閱讀:如何在 React 中使用 Ref官方 React 文檔

React Function Component: PropTypes(React 函數組件之:PropTypes)

PropTypes can be used for React Class Components and Function Components the same way. Once you have defined your component, you can assign it PropTypes to validate the incoming props of a component:

PropTypes 可以以相同的方式用於 React 類組件和函數組件。一旦你定義了你的組件,你可以分配它 PropTypes 來驗證組件的輸入 props:

import React from 'react';
import PropTypes from 'prop-types';

const App = () => {
  const greeting = 'Hello Function Component!';

  return <Headline value={greeting} />;
};

const Headline = ({ value }) => {
  return <h1>{value}</h1>;
};

Headline.propTypes = {
  value: PropTypes.string.isRequired,
};

export default App;

Note that you have to install the standalone React prop-types, because it has been removed from the React core library a while ago. If you want to learn more about PropTypes in React, check out the official documentation.

請注意,你必須安裝獨立的 React prop-types,因爲它已經從 React 核心庫中刪除了一段時間了。如果你想了解更多關於 React 中 PropTypes 的信息,請查看 官方文檔

In addition, previously you have seen the usage of default props for a Function Component. For the sake of completeness, this is another one:

此外,在前面你已經看到了函數組件默認 props 的使用。爲了完整起見,這是另一個例子:

import React from 'react';

const App = () => {
  const greeting = 'Hello Function Component!';

  return <Headline headline={greeting} />;
};

const Headline = ({ headline }) => {
  return <h1>{headline}</h1>;
};

Headline.defaultProps = {
  headline: 'Hello Component',
};

export default App;

Note that you can also use the default assignment when destructuring the value from the props in the function signature (e.g. const Headline = ({ headline = 'Hello Component' }) =>) or the || operator within the Function Component's body (e.g. return <h1>{headline || 'Hello Component'}</h1>;).

注意,你還可以在從函數簽名中的 props 中解構值時使用默認賦值(例如 const Headline = ({ headline = 'Hello Component' }) =>)或者 || 運算符,在函數組件主體中(例如 return <h1>{headline || 'Hello Component'}</h1>;)。

However, if you really want to go all-in with strongly typed components in React, you have to check out TypeScript which is briefly shown in the next section.

React Function Component: TypeScript(React 函數組件之:TypeScript)

If you are looking for a type system for your React application, you should give TypeScript for React Components a chance. A strongly typed language like TypeScript comes with many benefits for your developer experience ranging from IDE support to a more robust code base. You may wonder: How much different would a React Function Component with TypeScript be? Check out the following typed component:

如果你正在爲你的 React 應用程序尋找類型系統,你應該給 React 組件的 TypeScript 一個機會。像 TypeScript 這樣的強類型語言無論 IDE 支持還是更健壯的代碼庫,都會爲你的開發體驗帶來很多好處。你可能想知道:React 函數組件與 TypeScript 有多大的不同?查看以下類型的組件:

import React, { useState } from 'react';

const App = () => {
  const [greeting, setGreeting] = useState(
    'Hello Function Component!'
  );

  const handleChange = event => setGreeting(event.target.value);

  return (
    <Headline headline={greeting} onChangeHeadline={handleChange} />
  );
};

const Headline = ({
  headline,
  onChangeHeadline,
}: {
  headline: string,
  onChangeHeadline: Function,
}) => (
  <div>
    <h1>{headline}</h1>

    <input type="text" value={headline} onChange={onChangeHeadline} />
  </div>
);

export default App;

It only defines the incoming props as types. However, most of the time type inference just works out of the box. For instance, the use State Hook from the App component doesn't need to be typed, because from the initial value the types for greeting and setGreeting are inferred.

它只將傳入的 props 定義爲類型。然而,大多數時候類型推斷都是開箱即用的。例如,不需要輸入來自 App 組件的 useState Hook,因爲從初始值可以推斷出 greetingsetGreeting 的類型。

If you want to know how to get started with TypeScript in React, check out this comprehensive cheatsheet ranging from TypeScript setup to TypeScript recipes. It's well maintained and my go-to resource to learn more about it.

如果你想知道如何在 React 中使用 TypeScript,可以查看 comprehensive cheatsheet ranging from TypeScript setup to TypeScript recipes。它得到了很好的維護,是我瞭解它的首選資源。

React Function Component vs Class Component(React 的函數組件和類組件)

This section will not present you any performance benchmark for Class Components vs Functional Components, but a few words from my side about where React may go in the future.

本節不會向你展示任何類組件與函數組件的性能基準測試,但是我將簡要介紹未來 React 的發展方向。

Since React Hooks have been introduced in React, Function Components are not anymore behind Class Components feature-wise. You can have state, side-effects and lifecycle methods in React Function Components now. That's why I strongly believe React will move more towards Functional Components, because they are more lightweight than Class Components and offer a sophisticated API for reusable yet encapsulated logic with React Hooks.

自從在 React 中引入了 React 鉤子之後,函數組件的特性就不再落後於類組件了。你現在可以在 React 函數組件中使用狀態、副作用和生命週期方法。這就是爲什麼我堅信 React 將更多地向函數組件發展,因爲它們比類組件更輕量,併爲使用 React 鉤子的可重用但封裝的邏輯提供了成熟的 API。

For the sake of comparison, check out the implementation of the following Class Component vs Functional Component:

爲了比較,請查看以下類組件和函數組件的實現:

// Class Component

class App extends React.Component {
  constructor(props) {
    super(props);

    this.state = {
      value: localStorage.getItem('myValueInLocalStorage') || '',
    };
  }

  componentDidUpdate() {
    localStorage.setItem('myValueInLocalStorage', this.state.value);
  }

  onChange = event => {
    this.setState({ value: event.target.value });
  };

  render() {
    return (
      <div>
        <h1>Hello React ES6 Class Component!</h1>

        <input
          value={this.state.value}
          type="text"
          onChange={this.onChange}
        />

        <p>{this.state.value}</p>
      </div>
    );
  }
}

// Function Component

const App = () => {
  const [value, setValue] = React.useState(
    localStorage.getItem('myValueInLocalStorage') || '',
  );

  React.useEffect(() => {
    localStorage.setItem('myValueInLocalStorage', value);
  }, [value]);

  const onChange = event => setValue(event.target.value);

  return (
    <div>
      <h1>Hello React Function Component!</h1>

      <input value={value} type="text" onChange={onChange} />

      <p>{value}</p>
    </div>
  );
};

If you are interested in moving from Class Components to Function Components, check out this guide: A migration path from React Class Components to Function Components with React Hooks. However, there is no need to panic because you don't have to migrate all your React components now. Maybe it's a better idea to start implementing your future components as Function Components instead.

如果你對從類組件遷移到函數組件感興趣,請查看以下指南:使用 React 鉤子將 React 類組件遷移到函數組件的路徑。但是,沒有必要盲目,因爲你現在不必遷移所有的 React 組件。也許最好開始將未來的組件實現爲函數組件。

The article has shown you almost everything you need to know to get started with React Function Components. If you want to dig deeper into testing React Components for instance, check out this in-depth guide: Testing React Components. Anyway, I hope there have been a couple of best practices for using Functional Components in React as well. Let me know if anything is missing!

本文向你展示了開始使用 React 函數組件所需的幾乎所有知識。例如,如果你想更深入地研究 testing React 組件,請查看以下深入指南:Testing React Components。無論如何,我希望已經有了一些在 React 中使用函數組件的最佳實踐。如果有什麼遺漏,請告訴我!

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章