通过构建Paint App学习React Hooks

According to people in the know, React Hooks are hot, hot, hot. In this article, we follow Christian Jensen's 14-part tutorial to find out about the basics of this new feature of React. Follow along to find out more!

据知情人士称,React Hooks很热,很热。 在本文中,我们将按照Christian Jensen的14部分教程来了解React这个新功能的基础。 继续了解更多!

React Hooks we will learn in this course

介绍 (Intro)

Paint app we will build during this project

Hooks are new to the React library and allow us to share logic between components and make them reusable.

钩子是React库的新增功能,它使我们能够在组件之间共享逻辑并使它们可重用。

In this course, we will be building a paint app similar to Microsoft Paint, which will allow us to name our project, switch out colors, get a new batch of colors and of course paint.

在本课程中,我们将构建一个类似于Microsoft Paint的Paint应用程序,这将使我们能够为项目命名,切换颜色,获得新的一批颜色,当然还有Paint。

Scrimba allows you to pause screencasts at any time and play with the code. It's a great way to learn by doing!

Scrimba允许您随时暂停截屏视频并播放代码。 这是边干边学的好方法!

先决条件 (Prerequisites)

The course assumes some prior knowledge of ES6, JSX, State and Props, but no worries, we've got you covered - check out our Scrimba articles by hitting the links above.

本课程假定您具有ES6 , JSX,状态和道具的一些先验知识 ,但是不用担心,我们已经为您覆盖了-点击上面的链接,查看我们的Scrimba文章。

If you are completely new to React, be sure to check out our Scrimba React course

如果您是React的新手,请务必查看我们的Scrimba React课程

useState第1部分 (useState - Part 1)

First, we give our application a way to manage state using useState.

首先,我们为应用程序提供了一种使用useState管理状态的方法。

In our <Playground.js /> component, we declare a component called <Playground /> and create buttons to increment and decrement it. We then give useState an argument of (0) and use state restructuring to get state and setState (the function which updates the state) from our useState function. These are now renamed to count and setCount. We then render our count in the browser.

在我们的<Playground.js />组成部分,我们宣布了一个名为组件<Playground />并创建按钮来递增和递减它。 然后,将useState的参数useState (0),并使用状态重组从useState函数中获取statesetState (更新状态的函数)。 现在将它们重命名为countsetCount 。 然后,我们在浏览器中呈现计数。

Lastly, we render buttons which update the count using an inline function which will be triggered on the click.

最后,我们渲染按钮,该按钮使用嵌入式功能更新计数,该功能将在单击时触发。

Incrementing count with our buttons

To ensure our count is accurate, we pass a function to our setState function instead of a value. This function takes the current state as its argument, which is then updated:

为了确保计数准确,我们将一个函数而不是值传递给setState函数。 该函数将当前状态作为其参数,然后对其进行更新:

import React, { useState } from "react";
import randomColor from "randomcolor";export default function Playground() {const [count, setCount] = useState(0);return (<div>{count}<button onClick={() => setCount((currentCount) => currentCount - 1)}>-</button><button onClick={() => setCount((currentCount) => currentCount + 1)}>+</button></div>);
}

If you're worried about the performance of inline functions, take a look a this blog.

如果您担心内联函数的性能,请访问此博客。

useState第2部分 (useState - Part 2)

Now we add our name input to the <Name.js /> component so the user can name their project.

现在,我们将名称输入添加到<Name.js />组件中,以便用户可以命名其项目。

To set up<Name.js /> with a useState Hook, we need to import the Hook with a named import and then set our state up. Our state will be name and we will update it with setName. We then call useState and pass in an empty string as our default state value.

要使用useState Hook设置<Name.js /> ,我们需要使用命名的import导入Hook,然后设置状态。 我们的状态将是name ,我们将使用setName对其进行更新。 然后,我们调用useState并传入一个空字符串作为默认状态值。

We now need an input element with four properties. These are:

现在,我们需要一个具有四个属性的输入元素。 这些是:

  • value, which will always be the state name from above

    value ,它始终是上方的州name

  • onChange, which will use setState inline to update name by passing the value into setState

    onChange ,将使用setState内嵌到更新name由传递值到setState

  • onClick which uses setSelectionRange which takes a start index of 0 and end index of the length of the string to select the entire name, making it easier for the end-user to change the name.

    onClick使用setSelectionRange,它采用起始索引0和字符串长度的终止索引来选择整个名称,从而使最终用户更容易更改名称。

  • placeholder, which we set to 'Untitled'.

    placeholder ,我们将其设置为“无标题”。

import React, { useState } from "react";export default function Name() {const [name, setName] = useState("");return (<label className="header-name"><inputvalue={name}onChange={(e) => setName(e.target.value)}onClick={(e) => e.target.setSelectionRange(0, e.target.value.length)}placeholder="Untitled"/></label>);
}

We can now name our project and select the name to reset it with just one click:

现在,我们可以命名我们的项目,只需单击一下即可选择要重置的名称:

Project's name input in action.

useEffect (useEffect)

Currently, our Playground.js component is simply rendering a counter where can increment or decrement the count. Now we will update this so that every time the count is changed, the color of something is also changed.

当前,我们的Playground.js组件只是在渲染一个计数器,可以在其中增加或减少计数。 现在,我们将对其进行更新,以便每次更改计数时,事物的颜色也将更改。

We use the useState Hook to set up the initial color, which we set to null and the function to update it (setColor). Now, we set up useEffect to update this color. useEffect's first argument is setColor, which we want to set to a randomColor.

我们使用useState Hook设置初始颜色(将其设置为null和用于对其进行更新的函数( setColor )。 现在,我们设置useEffect来更新该颜色。 useEffect的第一个参数是setColor,我们要将其设置为randomColor

As we only want a change in count to trigger useEffect, we set this as the second argument. If the count value hasn't changed, the Hook will not run the effect and the color will remain the same.

由于我们只希望更改count来触发useEffect ,因此将其设置为第二个参数。 如果计数值未更改,则挂钩将不会运行效果,并且颜色将保持不变。

import React, { useState, useEffect } from "react";
import randomColor from "randomcolor";export default function Playground() {const [count, setCount] = useState(0);const [color, setColor] = useState(null);useEffect(() => {setColor(randomColor());}, [count]);return (<div style={{ borderTop: `10px solid ${color}` }}>{count}<button onClick={() => setCount((currentCount) => currentCount - 1)}>-</button><button onClick={() => setCount((currentCount) => currentCount + 1)}>+</button></div>);
}

Now, our color changes every time we increment or decrement our count.

现在,每次增加或减少计数时,颜色都会改变。

initial color

color changed by one increment

color changed by a second increment

color changed by one decrement

useStateuseEffect挑战 (useState & useEffect Challenge)

It's now time to test the skills we have acquired so far. In this screencast, a function which gets some random colors for us has been added to <Paint.js />:

现在是时候测试我们到目前为止掌握的技能了。 在此截屏视频中,向我们提供了一些随机颜色的函数已添加到<Paint.js />:

const getColors = () => {const baseColor = randomColor().slice(1);fetch(`https://www.thecolorapi.com/scheme?hex=${baseColor}&mode=monochrome`).then((res) => res.json()).then((res) => {setColors(res.colors.map((color) => color.hex.value));setActiveColor(res.colors[0].hex.value);});
};

Our task is to write the functions for setColors, which will give us an array of hex colors and setActiveColor, which will tell use what the active color is.

我们的任务是为setColors编写函数,这将为我们提供十六进制颜色的数组,并为setActiveColor编写函数,这将告诉使用什么是活动颜色。

If we set up everything correctly, the UI will update with five colors which we can click on to expand. We only need useState and useEffect for this test.

如果我们正确设置了所有内容,则用户界面将更新为五种颜色,可以单击以展开。 我们仅需要useStateuseEffect进行此测试。

useStateuseEffect解决方案# (useState & useEffect Solution#)

In this screencast, Christian walks us through how to give functionality to the <ColorPicker /> component. At the end of it, we now have some colors:

在此截屏视频中,克里斯汀(Christian)指导我们如何为<ColorPicker />组件赋予功能。 最后,我们有了一些颜色:

colors visible in UI

useEffect清理 (useEffect Clean Up)

Now we add a component called <WindowSize.js /> which will show the window width and height at the bottom of the screen when the user resizes the window. This then disappears after half a second.

现在,我们添加了一个名为<WindowSize.js />的组件,当用户调整窗口大小时,它将在屏幕底部显示窗口的宽度和高度。 然后半秒钟后消失。

When we set up a timer or an event listener, we also need to clean it up once the component unmounts. This requires two pieces of state - the window size and visibility of the <WindowSize /> component:

设置计时器或事件侦听器时,一旦组件卸载,我们还需要对其进行清理。 这需要两种状态-窗口大小和<WindowSize />组件的可见性:

export default function WindowSize() {const [[windowWidth, windowHeight], setWindowSize] = useState([window.innerWidth,window.innerHeight,]);const [visible, setVisible] = useState(false);
}

Now we set up our effect, which adds the event listener:

现在我们设置效果,添加事件侦听器:

useEffect(() => {const handleResize = () => {};window.addEventListener("resize", handleResize);
});

Next, we set up the cleanup phase. This returns the function and an empty array is passed in to tell it that useEffect should only run on the first mount. The cleanup will then run and remove the event listener:

接下来,我们设置清理阶段。 这将返回该函数,并传入一个空数组以告诉它useEffect应该仅在第一次安装时运行。 然后将运行清理并删除事件侦听器:

useEffect(() => {const handleResize = () => {};window.addEventListener("resize", handleResize);return () => window.removeEventListener("resize", handleResize);
}, []);

We now set up the window size, the visibility and the timer so that the the resize window appears and then disappears after 500 milliseconds:

现在,我们设置窗口大小,可见性和计时器,以使调整大小窗口出现,然后在500毫秒后消失:

const [visible, setVisible] = useState(false);
useEffect(() => {const handleResize = () => {setWindowSize([window.innerWidth, window.innerHeight]);setVisible(true);setTimeout(() => setVisible(false), 500);};window.addEventListener("resize", handleResize);return () => window.removeEventListener("resize", handleResize);
}, []);

However, we do not want to add a new timer every time the user resizes the window, so we also need to clean up the timer with clearTimeout(timeoutId):

但是,我们不想每次用户调整窗口大小时都添加一个新计时器,因此我们还需要使用clearTimeout(timeoutId)清除计时器:

timeoutId = setTimeout(() => setVisible(false), 500);

To give clearTimeout the timeoutId from the last time the function ran, we use closures, which means that we declare our timeoutId variable outside the handleResize function. This way, the variable is still available to the inner function. Every time the function runs, the previous timeout will be cleared and a new one will be set up.

为了给clearTimeout从上一次运行该函数起提供timeoutId ,我们使用了Closures ,这意味着我们在handleResize函数之外声明了timeoutId变量。 这样,变量仍可用于内部函数。 每次运行该功能时,上一个超时将被清除,并且将设置一个新的超时。

Lastly, we render our resize function to the browser.The final code can be seen in the screencast.

最后,我们将调整大小功能呈现给浏览器,最终的代码可以在截屏中看到。

Now, whenever the user resizes their window, the window size is set to the current window size, the visibility is set to true, and a timer is started to set the visibility to false after 500 milliseconds.

现在,每当用户调整窗口大小时,窗口大小将设置为当前窗口大小,可见性设置为true,并启动计时器以在500毫秒后将可见性设置为false。

browser with resize function rendered

useRef挑战 (useRef Challenge)

If you need to access to actual DOM elements in React, you may need to use Refs. React has a Hook, useRef, which is dedicated to Refs.

如果您需要访问React中的实际DOM元素,则可能需要使用Refs。 React有一个Hook, useRef ,专门用于Refs。

To use a Ref, it needs to be added to the element:

要使用Ref,需要将其添加到元素中:

<inputref={inputRef}type="range"onChange={(e) => setCount(e.target.value)}value={count}
/>

This input is a slider which updates the count and therefore the selected color. As the value is also tied to the count, the slider will also adjust if the count is changed via the buttons we added earlier.

此输入是一个滑块,可更新count并因此更新所选颜色。 由于该值也与计数相关,如果通过前面添加的按钮更改了计数,则滑块也会进行调整。

We have now declared our Ref, but we also need to set it up by calling useRef:

现在我们已经声明了Ref,但是我们还需要通过调用useRef来进行设置:

const inputRef = useRef();

In order to focus the input every time we change the count with the buttons, we simply add the necessary logic inside the effect which runs when the buttons are clicked:

为了每次使用按钮更改计数时都集中输入,我们只需在单击按钮时运行的效果内添加必要的逻辑即可:

useEffect(() => {setColor(randomColor())inputRef.current.focus()},

Slider in focus

Currently, the canvas is set to the height of the window itself, which makes it possible for the user to scroll within the canvas, which can lead to empty whitespace if the image is exported.

当前,画布设置为窗口本身的高度,这使用户可以在画布内滚动,如果导出图像,则可能导致空白。

Our challenge now is to ensure that the canvas of our paint app is only as big as the window minus the header height. To do this, we need to use useRef to get the height of the header and subtract it from the window's height.

现在我们面临的挑战是确保绘画应用程序的画布仅与窗口减去标题高度一样大。 为此,我们需要使用useRef来获取标题的高度,并将其从窗口的高度中减去。

useRef解决方案 (useRef Solution)

In this screencast Christian walks us through how to get the correct canvas height with useRef.

在此截屏视频中,Christian引导我们了解如何使用useRef获得正确的画布高度。

After this, the user is no longer able to scroll, except for a few pixels offset between Scrimba's browser and a regular browser. There is now no whitespace at the bottom of the image.

此后,除了Scrimba浏览器和常规浏览器之间的几个像素偏移之外,用户不再能够滚动。 现在,图像底部没有空格。

useCallbackuseMemo +挑战 (useCallback & useMemo + Challenge)

In this screencast, we are introduced to the concept of _ memoization_. This is when a pure function returns the same output from a calculation it has previous processed, rather than re-running the entire calculation:

在此截屏视频中,我们介绍了_ memoization_的概念。 这是当纯函数从先前处理过的计算返回相同的输出时,而不是重新运行整个计算时:

function Calculate(num) {// first call, num === 3... ok I will calculate thatreturn fetchComplicatedAlgorithmToAdd47(3); // returns 50 after a while// second call, num === 5... ok I guess I have to calculate that tooreturn fetchComplicatedAlgorithmToAdd47(5); // returns 52 after a while// third call, num === 3... WAIT, I've seen this before! I know this one!return 50; // immediately
}

React provides two Hooks which allow us to use memoization: useCallback and useMemo.

React提供了两个Hook,它们允许我们使用useCallbackuseCallbackuseMemo

useCallback ### (useCallback###)

We start off with a very simple component in Playground.js which renders the number of times the function has rendered:

我们从Playground.js中的一个非常简单的组件开始,该组件呈现该函数呈现的次数:

function Calculate(num) {const renderCount = useRef(1);return <div>{renderCount.current++}</div>;
}

render count in the browser.

Now let's say that the component should only render when the count changes, but not when the color changes. To achieve this, we could use useCallback. We assign the result of useCallback to a variable called calculate:

现在让我们说,该组件仅在计数改变时才渲染,而在颜色改变时不应该渲染。 为此,我们可以使用useCallback 。 我们的结果分配useCallback给一个变量称为calculate

const calculate = useCallback(<Calculate />, [count]);

We will now render our new calculate variable instead of the <Calculate /> component. Now, the component only renders when the count is changed, and not when the 'Change Color' button is clicked.

现在,我们将呈现新的calculate变量,而不是<Calculate />组件。 现在,该组件仅在更改计数时才渲染,而不是在单击“更改颜色”按钮时才渲染。

We also need to render our <Calculate /> component instead of the variable we previously used and create a callback function. We use useCallback and assign it to a variable called cb. The count is the only dependency, meaning that if the count changes we will get a new function instance:

我们还需要呈现我们的<Calculate />组件而不是先前使用的变量,并创建一个回调函数。 我们使用useCallback并将其分配给名为cb的变量。 count是唯一的依赖项,这意味着如果计数发生变化,我们将获得一个新的函数实例:

const cb = useCallback((num) => console.log(num), [count]);

Now we pass in a number (which is set to the count) to the Calculate component and the callback function, which we log to the console. Whenever the Calculate component re-renders (i.e. when the plus and minus buttons are clicked), the current count will be logged to the console.

现在,我们将一个数字(设置为count)传递给Calculate组件和回调函数,并将其登录到控制台。 每当重新Calculate组件时(即单击加号和减号按钮时),当前计数都将记录到控制台。

However, with this method, the count is also logged to the console when we click the 'Change Color' button. This is because we are using memoization for our console.log function, but not for our actual component, meaning that is not checking whether the callback function is the same as a previous one.

但是,使用这种方法,当我们单击“更改颜色”按钮时,计数也会记录到控制台。 这是因为我们正在为console.log函数使用备忘录,但没有为实际组件使用备忘录,这意味着不检查回调函数是否与上一个相同。

React.memo (React.memo)

To solve this, we add React.memo to the Calculate component. Now, it will check the inputs and see whether they are the same, and will not render if so:

为了解决这个问题,我们将React.memo添加到Calculate组件中。 现在,它将检查输入并查看它们是否相同,如果相同,将不进行渲染:

const Calculate = React.memo(({ cb, num }) => {cb(num);const renderCount = useRef(1);return <div>{renderCount.current++}</div>;
});

The 'Change Color' button now no longer logs the count to the console.

现在,“更改颜色”按钮不再将计数记录到控制台。

useMemo ### (useMemo###)

To see what useMemo can do, we add a useCallback call right next to a useMemo call:

要查看useMemo可以做什么,我们在useCallback调用旁边添加一个useMemo调用:

useCallback(() => console.log("useCallback"));
useMemo(() => console.log("useMemo"));

This tells us that useMemo is used every time the function renders. This is because useCallback returns the functions, whereas useMemo returns the result of the function:

这告诉我们每次函数渲染时都会使用useMemo 。 这是因为useCallback返回函数,而useMemo返回函数结果:

useCallback(() => console.log("useCallback")); // return the function
useMemo(() => console.log("useMemo")); // return the result of the function

useMemo can be used for some expensive functions which you want to memoize. UseCallback, on the other hand, is better for passing a callback into a component when you don't want to render the component unnecessarily.

useMemo可用于一些您想useMemo昂贵功能。 另一方面, UseCallback可以在您不想不必要地呈现组件时将回调传递给组件。

The screencast finishes with a new challenge. Our paint app currently offers only a few colors to work with. Our challenge is to add some functionality to a newly-added refresh button so that the user can click the button and get some new colors. This should take place in RefreshButton.js, which is currently taking in a callback and should be calling that callback when the refresh button is clicked. Our challenge is to pass in the callback using useCallback or useMemo.

截屏视频结束了新的挑战。 我们的绘画应用程序目前仅提供几种颜色供您使用。 我们的挑战是为新添加的刷新按钮添加一些功能,以便用户单击该按钮并获得一些新的颜色。 这应该在RefreshButton.js ,该当前正在接受回调,并且在单击刷新按钮时应调用该回调。 我们的挑战是使用useCallbackuseMemo传递回调。

Refresh button which requires functionality

As a bonus challenge, we are also asked to use React.memo to memoize the <Name /> component, which is currently rendering unnecessarily every time we change our colors.

作为一个额外的挑战,我们还被要求使用React.memo <Name />组件,该组件当前每次更改颜色时都不必要地渲染。

useCallback解决方案 (useCallback Solution)

Now, Christian walks us through the solution to the previous challenges, follow him in this marvellous screencast.

现在,克里斯汀(Christian)带领我们完成了先前挑战的解决方案,并在这个出色的截屏视频中跟随他。

At the end of the screencast, our refresh button is now supplying shiny new colors when clicked:

在截屏视频的结尾,单击时,我们的刷新按钮现在将提供闪亮的新颜色:

Refresh button changing colors - 1

Refresh button changing colors - 2

定制钩 (Custom Hooks)

Here, we learn about custom Hooks by refactoring the <WindowSize /> component into a Hook. This is great for reusability.

在这里,我们通过将<WindowSize />组件重构为挂钩来了解自定义挂钩。 这对于可重用性非常有用。

Currently, <WindowSize /> is handling two different sets of state; the window size and visibility. As visibility might not be needed in future uses of <WindowSize />, we move its logic into our <Paint /> component, which is also where we will use our useWindowSize Hook.

当前, <WindowSize />正在处理两个不同的状态集。 窗口大小和可见性。 由于将来使用<WindowSize />可能不需要可见性,因此我们将其逻辑移到<Paint />组件中,这也是我们将使用useWindowSize Hook的地方。

The following lines are removed from WindowSize.js:

以下行已从WindowSize.js中删除:

let timeoutId;
///
setVisible(true);
clearTimeout(timeoutId);
timeoutId = setTimeout(() => setVisible(false), 500);

Additionally, the following lines now need to be returned from <Paint.js /> instead of <WindowSize />:

此外,现在需要从<Paint.js />而不是<WindowSize />返回以下行:

<div className={`window-size ${visible ? "" : "hidden"}`}>{windowWidth} x {windowHeight}
</div>

The window width and height will be returned from <WindowSize />:

窗口的宽度和高度将从<WindowSize />返回:

return [windowWidth, windowHeight];

To make the windowWidth and windowHeight variables available, we add the following code to <Paint.js />:

为了使windowWidthwindowHeight变量可用,我们将以下代码添加到<Paint.js />

const [windowWidth, windowHeight] = useWindowSize();

To implement the visibility logic so that we can show and hide the window size as necessary, we pass in a callback to our useWindowSize Hook and use a Ref to make timeoutID available between renders:

为了实现可见性逻辑,以便我们可以根据需要显示和隐藏窗口大小,我们将回调传递给useWindowSize Hook,并使用Ref使timeoutID在渲染之间可用:

let timeoutId = useRef();
const [windowWidth, windowHeight] = useWindowSize(() => {setVisible(true);clearTimeout(timeoutId.current);timeoutId.current = setTimeout(() => setVisible(false), 500);
});

We can now call this when we need to from <WindowSize />:

现在,我们可以在<WindowSize />调用它:

export default function useWindowSize(cb) {const [[windowWidth, windowHeight], setWindowSize] = useState([window.innerWidth,window.innerHeight,]);useEffect(() => {const handleResize = () => {cb();setWindowSize([window.innerWidth, window.innerHeight]);};window.addEventListener("resize", handleResize);return () => window.removeEventListener("resize", handleResize);}, []);return [windowWidth, windowHeight];
}

We now have the same functionality as before but the <WindowSize /> logic is in a reusable Hook.

现在,我们具有与以前相同的功能,但是<WindowSize />逻辑位于可重用的Hook中。

The lessons ends with another challenge - to convert the <Canvas /> component into a function which uses Hooks instead of lifecycle methods.

这些课程以另一个挑战结束-将<Canvas />组件转换为使用Hooks而非生命周期方法的函数。

使用Hooks构建绘画应用 (Building the paint app with Hooks)

This screencast walks us through how to convert <Canvas /> into a functional component using Hooks. It also shows us how to refactor our app to make it much cleaner and more readable. A big advantage of using Hooks is that all related logic is next to each other, in contrast to our old components in which related logic items were separated from each other.

该截屏视频指导我们如何使用Hooks将<Canvas />转换为功能组件。 它还向我们展示了如何重构我们的应用程序,使其更加整洁和可读性强。 使用Hooks的一大优势是,所有相关的逻辑都彼此相邻,这与我们以前的旧组件不同,在旧的组件中,相关的逻辑项彼此分开。

At the end of the screencast, our paint app is finally finished and we are ready to paint our masterpieces:

在截屏视频的结尾,我们的绘画应用程序终于完成了,我们可以绘画我们的杰作了:

using our paint app

奥托罗 (Outro)

We have now finished the React Hooks course. We have learnt about:

现在,我们已经完成了React Hooks课程。 我们了解到:

  • useState, which manages state

    useState ,它管理状态

  • useEffect, which does side effects,

    useEffect ,它有副作用,

  • useRef, which gets references to DOM elements and keeps values across renders

    useRef ,它获取对DOM元素的引用并在渲染器之间保留值

  • useCallback, which creates functions which don't need to be created on every render

    useCallback ,它创建不需要在每个渲染器上都创建的函数

  • useMemo, which memoizes expensive computations

    useMemo ,用于记忆昂贵的计算

  • React.Memo, which can go around a React component and memoize it

    React.Memo ,它可以绕过React组件并对其进行React.Memo

  • custom Hooks, which allow us to create our own reusable logic.

    custom Hooks ,它允许我们创建自己的可重用逻辑。

There are two rules to keep in mind when using any of these Hooks:

使用这些挂钩中的任何两个时,都需要牢记两个规则:

  1. Only call Hooks at the top level of the React component, i.e. not within if blocks or anything similar.

    仅在React组件的顶层调用Hook,即不在if块或类似内容之内。
  2. Only call Hooks from React functions, not your own custom functions.

    仅从React函数调用Hook,而不是您自己的自定义函数。

Congratulations on following the tutorial and learning all the skills used in this project. To further your learning, check out Scrimba's free, six-hour Learn React for Free course which aims to make you a React wizard!

恭喜您按照教程学习了本项目中使用的所有技能。 为了进一步学习,请查看Scrimba的免费六小时免费学习React免费课程,该课程旨在使您成为React向导!

Happy coding!

编码愉快!

翻译自: https://www.freecodecamp.org/news/learn-react-hooks-by-building-a-paint-app/

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/390165.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

正则表达式 匹配常用手机号 (13、15\17\18开头的十一位手机号)

原文:正则表达式 匹配常用手机号 &#xff08;13、15\17\18开头的十一位手机号&#xff09;^1[3578]\d{9}$ ^1表示以1开头&#xff0c;[3578]表示第二位的数字为3578中的任意一个&#xff0c;\d{9}表示0~9范围内的数字匹配九次,$表示结束&#xff0c;12位以上的数字不匹配。

Npoi导出excel整理(附源码)

前些日子做了一个简单的winform程序&#xff0c;需要导出的功能&#xff0c;刚开始省事直接使用微软的组件&#xff0c;但是导出之后发现效率极其低下&#xff0c;绝对像web那样使用npoi组件&#xff0c;因此简单的进行了整理&#xff0c;包括直接根据DataTable导出excel及Data…

44. 通配符匹配

44. 通配符匹配 给定一个字符串 (s) 和一个字符模式 &#xff0c;实现一个支持 ‘?’ 和 ‘*’ 的通配符匹配。 ? 可以匹配任何单个字符。 * 可以匹配任意字符串&#xff08;包括空字符串&#xff09;。 两个字符串完全匹配才算匹配成功。说明: s 可能为空&#xff0c;且…

递归javascript_使用freeCodeCamp挑战解释了JavaScript中的递归

递归javascriptIn this article I will touch on a few important ideas to help you understand Recursion in JavaScript. I’m not going to give a full definition here, but you can take a look at what Wikipedia has to say. 在本文中&#xff0c;我将介绍一些重要的想…

入库成本与目标成本对比报表中我学到的东西

1、SQL方面&#xff1a; &#xff08;1&#xff09;、用DECODE函数解决除数为零的情况 具体语法&#xff1a; DECODE&#xff08;参数&#xff0c;0&#xff0c;1&#xff0c;参数&#xff09; ->DECODE(TAB1.A8&#xff0c;0&#xff0c;1&#xff0c;TAB1.A8) &#xff08…

J - Borg Maze

J - Borg Maze 思路&#xff1a;bfs最小生成树。#include<queue> #include<cstdio> #include<cstring> #include<iostream> #include<algorithm> #define MAXN 110 using namespace std; int fa[MAXN]; struct nond{int x,y,z; }v[MAXN*MAXN]; s…

1095. 山脉数组中查找目标值

1095. 山脉数组中查找目标值 &#xff08;这是一个 交互式问题 &#xff09; 给你一个 山脉数组 mountainArr&#xff0c;请你返回能够使得 mountainArr.get(index) 等于 target 最小 的下标 index 值。 如果不存在这样的下标 index&#xff0c;就请返回 -1。 何为山脉数组…

【小摘抄】关于C++11下 string各类用法(持续更新)

http://blog.csdn.net/autocyz/article/details/42391155 提供了最简单的详解 下列对本人近期开发中的一些心得体会进行摘抄 1.string按照字符进行截取 示例代码&#xff1a; string teststring "#12313#kajlkfdsa";//通讯消息示例&#xff0c;结合string的内置函数…

sql综合练习题

一、表关系 年级表&#xff1a;class_grade create table class_grade(gid int primary key auto_increment,gname varchar(20) not null); insert into class_grade(gname) values(一年级),(二年级),(三年级); 班级表&#xff1a;class create table class(cid int primary ke…

javascript原型_在JavaScript中冻结原型时会发生什么

javascript原型Have you wondered what happens when you freeze the prototype of an object? Lets find out together.您是否想过冻结对象的原型时会发生什么&#xff1f; 让我们一起找出答案。 对象 (Objects) In JavaScript, objects are dynamic collections of propert…

迟来的2017总结

明天就是年后第一天上班了&#xff08;过年期间请了6天假&#xff09;&#xff0c; 打算今天写一下2017的总结&#xff0c;本来还想写2018的愿景的&#xff0c;不过想想还是算了&#xff0c;现在没什么想法&#xff0c;不想敷衍了事。 先贴一个2017的提升计划&#xff1a; http…

tomcat启动卡住

新部署的项目启动tomcat后一直停在org.apache.catalina.core.StandardEngine.startInternal Starting Servlet Engine: Apache Tomcat/8.5.16&#xff0c;卡在了org.apache.catalina.startup.HostConfig.deployDirectory Deploying web application directory [/opt/tomcat/web…

怎样准备阿里技术面试_如何准备技术面试

怎样准备阿里技术面试In June 2020 I watched an inspiring talk by Anthony D. Mays, a technical coach and founder at Morgan Latimerco. He came on a Facebook Developer Circles Benin live session and talked about how to prepare for a technical interview. 2020年…

通过一个简单例子理解 RecyclerView.ItemDecoration

一、前言 RecyclerView 是从5.0推出的 MD 风格的控件。RecyclerView 之前有 ListView、GridView&#xff0c;但是功能很有限&#xff0c;例如 ListView 只能实现垂直方向上的滑动等。但是存在则合理&#xff0c;ListView 却没有被官方标记为 Deprecated&#xff0c;有兴趣的同学…

Entity Framework Logging and Intercepting Database Operations (EF6 Onwards)

参考官方文档&#xff1a;https://msdn.microsoft.com/en-us/library/dn469464(vvs.113).aspx转载于:https://www.cnblogs.com/liandy0906/p/8473110.html

面试题 17.14. 最小K个数

面试题 17.14. 最小K个数 设计一个算法&#xff0c;找出数组中最小的k个数。以任意顺序返回这k个数均可。 示例&#xff1a; 输入&#xff1a; arr [1,3,5,7,2,4,6,8], k 4 输出&#xff1a; [1,2,3,4] 提示&#xff1a; 0 < len(arr) < 1000000 < k < min(1…

这是您现在可以免费获得的115张Coursera证书(在冠状病毒大流行期间)

At the end of March, the world’s largest Massive Open Online Course provider Coursera announced that they are offering 100 free courses in response to the impact of the COVID-19 pandemic. 3月底&#xff0c;全球最大的大规模在线公开课程提供商Coursera 宣布 &a…

由浅入深理解----java反射技术

java反射机制详解 java反射机制是在运行状态下&#xff0c;对任意一个类可以获取该类的属性和方法&#xff0c;对任意一个对象可以调用其属性和方法。这种动态的获取信息和调用对象的方法的功能称为java的反射机制 class<?>类&#xff0c;在java.lang包下面&#xff0c;…

【VMware vSAN 6.6】5.5.Update Manager:vSAN硬件服务器解决方案

目录 1. 简介 1.1.适用于HCI的企业级存储2. 体系结构 2.1.带有本地存储的服务器2.2.存储控制器虚拟系统套装的缺点2.3.vSAN在vSphere Hypervisor中自带2.4.集群类型2.5.硬件部署选项3. 启用vSAN 3.1.启用vSAN3.2.轻松安装3.3.主动测试4. 可用性 4.1.对象和组件安置4.2.重新构建…

5848. 树上的操作

给你一棵 n 个节点的树&#xff0c;编号从 0 到 n - 1 &#xff0c;以父节点数组 parent 的形式给出&#xff0c;其中 parent[i] 是第 i 个节点的父节点。树的根节点为 0 号节点&#xff0c;所以 parent[0] -1 &#xff0c;因为它没有父节点。你想要设计一个数据结构实现树里面…