通过构建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,一经查实,立即删除!

相关文章

Npoi导出excel整理(附源码)

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

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

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…

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

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

【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.重新构建…

32位JDK和64位JDK

32位和64位系统在计算机领域中常常提及&#xff0c;但是仍然很多人不知道32位和64位的区别&#xff0c;所以本人在网上整理了一些资料&#xff0c;并希望可以与大家一起分享。对于32位和64位之分&#xff0c;本文将分别从处理器&#xff0c;操作系统&#xff0c;JVM进行讲解。 …

中小企业如何选择OA协同办公产品?最全的对比都在这里了

对于中小企业来说&#xff0c;传统的OA 产品&#xff0c;如泛微、蓝凌、致远、华天动力等存在价格高、使用成本高、二次开发难等特点&#xff0c;并不适合企业的协同管理。 国内OA市场也出现了一批轻便、低价的OA产品&#xff0c;本文针对以下几款适合中小企业的OA产品在功能、…

Elasticsearch学习(2)—— 常见术语

为什么80%的码农都做不了架构师&#xff1f;>>> cluster (集群)&#xff1a;一个或多个拥有同一个集群名称的节点组成了一个集群。每个集群都会自动选出一个主节点&#xff0c;如果该主节点故障&#xff0c;则集群会自动选出新的主节点来替换故障节点。 node (节点…

IntelliJ IDEA 运行 Maven 项目

1.官方文档说IntelliJ IDEA已经自身集成了maven&#xff0c;则不用劳心去下载maven 2.导入一个程序&#xff0c;看是否是maven程序的关键在于工程之中有没有pom.xml这个文件&#xff0c;比如这里 3.为这个工程配置好服务器3.1 点击“Edit Configurations”3.2 进入Run/Debug C…

资深老鸟整理,Java接口自动化测试总结,从0到1自动化...

这几年接口自动化变得越来越热门&#xff0c;相对比于UI自动化&#xff0c;接口自动化有一些优势 1&#xff09;运行比UI更稳定&#xff0c;让BUG更容易定位 2&#xff09;UI自动化维护成本太高&#xff0c;接口相对低一些 接口测试其实有很多方式&#xff0c;主要有两种&…

pdf 字体和图片抽取

2019独角兽企业重金招聘Python工程师标准>>> #安装mutoos sudo apt-get install mupdf-tools #抽取字体 mutool extract LTN20180531052_C.pdf 转载于:https://my.oschina.net/colin86/blog/1842412

扫盲丨关于区块链你需要了解的所有概念

扫盲丨关于区块链你需要了解的所有概念 如今存储信息的方式有什么问题&#xff1f; 目前&#xff0c;支配我们生活的数据大部分都储存在一个地方&#xff0c;不论是在私人服务器、云、图书馆或档案馆的纸上。大多数情况下这很好&#xff0c;但这也容易受到攻击。 最近有消息称&…

SpringBoot环境切换

2019独角兽企业重金招聘Python工程师标准>>> 1.在application.yml中配置&#xff0c;如果java -jar banke-boot-bd-api-0.0.1-SNAPSHOT.jar&#xff0c;那么就是已application-test作为启动的配置文件启动 spring: profiles: active: test 2.如果java -jar banke-bo…

[No0000B0]ReSharper操作指南1/16-入门与简介

安装指南 在安装之前&#xff0c;您可能需要检查系统要求。 ReSharper是一个VisualStudio扩展。它支持VisualStudio2010,2012,2013,2015和2017.安装完成后&#xff0c;您将在VisualStudio的主菜单中找到新的ReSharper条目。大多数ReSharper命令都可以在这个菜单中找到。但是&a…

数据结构学习笔记(一)——《大话数据结构》

第一章 数据结构绪论 基本概念和术语 数据 描述客观事物的符号&#xff0c;计算机中可以操作的对象&#xff0c;能被计算机识别并输入给计算机处理的符号的集合。包括整型、实型等数值类型和字符、声音、图像、视频等非数值类型。 数据元素 组成数据的、有一定意义的基本单位&a…

java的垃圾回收机制包括:主流回收算法和收集器(jvm的一个主要优化方向)

2019独角兽企业重金招聘Python工程师标准>>> java的垃圾回收机制是java语言的一大特色&#xff0c;解放了开发人员对内存的复杂控制&#xff0c;但如果你想要一个高级java开发人员&#xff0c;还是需要知道其机制&#xff0c;所谓不仅要会用还要知道其原理这样才能用…

@hot热加载修饰器导致static静态属性丢失(已解决)

react开发的时候&#xff0c;引入热加载&#xff0c;用了修饰器的引入方式&#xff0c;发现了一个很有意思的问题&#xff0c;网上并没有相关文章&#xff0c;所以抛出来探讨下。 一段很简单的测试代码。但是经过babel编码后&#xff0c;变得很有意思。假设编码成es2016&#x…

学习vue.js的自我梳理笔记

基本语法格式&#xff1a; <script> new Vue({ el: #app, data: { url: http://www.runoob.com } }) </script> 指令 【指令是带有 v- 前缀的特殊属性。】 判断 <p v-if"seen">现在你看到我了</p> 参数 <a v-bind:href"url"&…

Spring+jpaNo transactional EntityManager available

2019独角兽企业重金招聘Python工程师标准>>> TransactionRequiredException: No transactional EntityManager availableEntityManager执行以下方法(refresh, persist, flush, joinTransaction, remove, merge) 都需要需要事务if (transactionRequiringMethods.cont…

01_Struts2概述及环境搭建

1.Struts2概述&#xff1a;Struts2是一个用来开发MVC应用程序的框架。Struts2提供了web应用程序开发过程中一些常见问题的解决方案;对用户输入的数据进行合法性验证统一的布局可扩展性国际化和本地化支持Ajax表单的重复提交文件的上传和下载... ...2.Struts2相对于Struts1的优势…

高可用性、负载均衡的mysql集群解决方案

2019独角兽企业重金招聘Python工程师标准>>> 一、为什么需要mysql集群&#xff1f; 一个庞大的分布式系统的性能瓶颈中&#xff0c;最脆弱的就是连接。连接有两个&#xff0c;一个是客户端与后端的连接&#xff0c;另一个是后端与数据库的连接。简单如图下两个蓝色框…