目录
1. Making Sense of React Hooks?
1.1. Why Hooks?
1.2. Do Hooks Make React Bloated?
1.3. What Are Hooks, Exactly?
1.4. Show Me Some Code!
2. What's going to happen to render props?
3. Migrating from lifecycle methods to hooks
3.1. From componentDidMount to useMount
3.2. From componentWillUnmount to useUnmount
3.3. From componentDidUpdate to useUpdate
3.4. From shouldComponentUpdate to memo and useMemo
3.5. From getDerivedStateFromProps to… render
3.6. From componentDidCatch to… nothing
1. Making Sense of React Hooks?
1.1. Why Hooks?
We know that components and top-down data flow help us organize a large UI into small, independent, reusable pieces.However, we often can’t break complex components down any further because the logic is stateful and can’t be extracted to a function or another component. Sometimes that’s what people mean when they say React doesn’t let them “separate concerns.”
These cases are very common and include animations, form handling, connecting to external data sources, and many other things we want to do from our components. When we try to solve these use cases with components alone, we usually end up with:
We think Hooks are our best shot at solving all of these problems.Hooks let us organize the logic inside a component into reusable isolated units:
Hooks apply the React philosophy (explicit data flow and composition) inside a component, rather than just between the components. That’s why I feel that Hooks are a natural fit for the React component model.
Unlike patterns like render props or higher-order components, Hooks don’t introduce unnecessary nesting into your component tree. They also don’t suffer from the drawbacks of mixins.
1.2. Do Hooks Make React Bloated?
If the React community embraces the Hooks proposal, it will reduce the number of concepts you need to juggle when writing React applications. Hooks let you always use functions instead of having to constantly switch between functions, classes, higher-order components, and render props.
The Hooks proposal doesn’t include any breaking changes. Your existing code would keep on working even if you adopted Hooks in the newly written components. In fact, that’s exactly what we recommend — don’t do any big rewrites!
1.3. What Are Hooks, Exactly?
To understand Hooks, we need to take a step back and think about code reuse.
Today, there are a lot of ways to reuse logic in React apps. We can write simple functions and call them to calculate something. We can also write components (which themselves could be functions or classes). Components are more powerful, but they have to render some UI. This makes them inconvenient for sharing non-visual logic. This is how we end up with complex patterns like render props and higher-order components. Wouldn’t React be simpler if there was just one common way to reuse code instead of so many?
Functions seem to be a perfect mechanism for code reuse. Moving logic between functions takes the least amount of effort. However, functions can’t have local React state inside them. You can’t extract behavior like “watch window size and update the state” or “animate a value over time” from a class component without restructuring your code or introducing an abstraction like Observables. Both approaches hurt the simplicity that we like about React.
Hooks solve exactly that problem. Hooks let you use React features (like state) from a function — by doing a single function call. React provides a few built-in Hooks exposing the “building blocks” of React: state, lifecycle, and context.
Since Hooks are regular JavaScript functions, you can combine built-in Hooks provided by React into your own “custom Hooks”. This lets you turn complex problems into one-liners and share them across your application or with the React community:
Note that custom Hooks are not technically a React feature. The possibility of writing your own Hooks naturally follows from the way Hooks are designed.
1.4. Show Me Some Code!
Let’s say we want to subscribe a component to the current window width (for example, to display different content on a narrow viewport).
There are several ways you can write this kind of code today. They involve writing a class, setting up some lifecycle methods, or maybe even extracting a render prop or a higher-order component if you want to reuse it between components. But I think nothing quite beats this:
If you read this code, it does exactly what it says. We use the window width in our component, and React re-renders our component if it changes. And that’s the goal of Hooks — to make components truly declarative even if they contain state and side effects.
Let’s look at how we could implement this custom Hook. We’d use the React local state to keep the current window width, and use a side effect to set that state when the window resizes:
As you can see above, the built-in React Hooks like useState and useEffect serve as the basic building blocks. We can use them from our components directly, or we can combine them into custom Hooks like useWindowWidth. Using custom Hooks feels as idiomatic as using React’s built-in API.
Hooks are fully encapsulated — each time you call a Hook, it gets isolated local state within the currently executing component. This doesn’t matter for this particular example (window width is the same for all components!), but it’s what makes Hooks so powerful. They’re not a way to share state — but a way to share stateful logic. We don’t want to break the top-down data flow!
Each Hook may contain some local state and side effects. You can pass data between multiple Hooks just like you normally do between functions. They can take arguments and return values because they are JavaScript functions.
The ability to pass data between Hooks make them a great fit for expressing animations, data subscriptions, form management, and other stateful abstractions. Unlike render props or higher-order components, Hooks don’t create a “false hierarchy” in your render tree.They’re more like a flat list of “memory cells” attached to a component. No extra layers.
2. What's going to happen to render props?
And React Hooks are WAY simpler than class components + render props.
Here's a typical usage of react-toggled:
function App() {
return (
<Toggle>
{({on, toggle}) => <button onClick={toggle}>{on ? 'on' : 'off'}</button>}
</Toggle>
)
}
If all we wanted was simple toggle functionality, our hook version would be:
function useToggle(initialOn = false) {
const [on, setOn] = useState(initialOn)
const toggle = () => setOn(!on)
return {on, toggle}
}
Then people could use that like so:
function App() {
const {on, toggle} = useToggle()
return <button onClick={toggle}>{on ? 'on' : 'off'}</button>
}
Cool! A lot simpler!
3. Migrating from lifecycle methods to hooks
3.1. From componentDidMount to useMount
The componentDidMount method is a method that usually triggers side effects, once, when the component is mounted.
The equivalent hook would be something that triggers a side effect when the SFC is called for the first time and stops being called after that. The hook we can use for that is called useEffect . By default, it is called everytime the component is called (i.e. when mounting and updating). If we take a closer look at the documentation, we see that it can be “memoized” by passing an array of values as the second argument. Moreover, passing an empty array will tell React to call it once and never again. An empty array is like saying “there is no value that would make this function result outdated when it changes”. So our componentDidUpdate implementation would simply be:
import { useEffect } from 'react'
function useMount(fn) {
useEffect(() => void fn(), [])
}
By calling the function with void , we make sure that we return undefined to the hook (we will see why it matters when we try to mimic componentWillUnmount. Since we pass an empty array, React will apply the effect once and never update (as no value is observed by the memoization process). Therefore, it will be called at when the component is mounting.
3.2. From componentWillUnmount to useUnmount
The componentWillUnmount method is also used to trigger side effect, but this time when the component is unmounted. It is not an uncommon case to use it in combination with componentDidMount, for example to create and delete event listeners.
As we saw in the previous chapter, we can prevent our hook from triggering on every render by passing an empty array of value to observe. By doing so we tell it be called once on mounting then never again. But what if we dont want to call him once at mount-time, but call something at unmount-time ? Well, the documentation from useEffect tells us that any function returned by the function called by the useEffect hook will be called when the component is unmounted. So could we could just do something like this:
import { useEffect } from 'react'
function useUnmount(fn) {
useEffect(() => fn, [])
}
By calling our arrow function, we do nothing and return the reference to the function we want React to call when it unmounts our component. Once again, passing an empty array ensure we do not repeatedly call our function for every render (since it does nothing anyway, it would be a waste of ressources).
3.3. From componentDidUpdate to useUpdate
Last but not least, we would like to mimic the behaviour of componentDidUpdate, which is to be called on every rendering except the initial one.
We saw in the previous examples that useEffect takes a second parameter that tells to call the function we pass under certain condition. By not passing this argument, we tell React that ”nothing should ever prevent this effect from being called at render”. That includes the first one, so we need to take care of this one in particular but the rest of the time, React will call our effect regardless of any condition. For the initial rendering, we would need to declare a value (a boolean) that tells us if we’re in the initial rendering or any subsequent one. This value should not trigger and new rendering when updated and should be accessible from our useUpdate function.
This is a job that can be fulfilled by the useRef method. Think of useRef as a useState that does not trigger a re-rendering of our component. So all we need to do is tweak our useMount function a little:
import { useRef, useEffect } from 'react'
function useUpdate(fn) {
const mounting = useRef(true)
useEffect(() => {
if (mounting.current) {
mounting.current = false
} else {
fn()
}
})
}
In the code above we:
3.4. From shouldComponentUpdate to memo and useMemo
shouldComponentUpdate will not be as straight forward as the previous ones. With React 16.7 you can either:
3.5. From getDerivedStateFromProps to… render
Since your state is now located directly in your render function (since your component is the render function), there is no need for a hook to update a state.
3.6. From componentDidCatch to… nothing
Sadly, at this point there is no equivalent to componentDidCatch, so the only solution is to make a classic React.Component.
参考:
Making Sense of React Hooks: https://medium.com/@dan_abramov/making-sense-of-react-hooks-fdbde8803889 From React.Component to hooks: https://medium.com/@dispix/from-react-component-to-hooks-b50241334365 React Hooks: What's going to happen to my tests?: https://kentcdodds.com/blog/react-hooks-whats-going-to-happen-to-my-tests React Hooks: What's going to happen to render props?: https://kentcdodds.com/blog/react-hooks-whats-going-to-happen-to-render-props State Management with React Hooks — No Redux or Context API: https://medium.com/javascript-in-plain-english/state-management-with-react-hooks-no-redux-or-context-api-8b3035ceecf8 How to fetch data with React Hooks?: https://www.robinwieruch.de/react-hooks-fetch-data Primer on React Hooks: https://testdriven.io/blog/react-hooks-primer/ React Hooks - A deeper dive featuring useContext and useReducer: https://testdriven.io/blog/react-hooks-advanced/ Using Custom React Hooks to Simplify Forms: https://upmostly.com/tutorials/using-custom-react-hooks-simplify-forms The Guide to Learning React Hooks: https://www.telerik.com/kendo-react-ui/react-hooks-guide/ React by Example: Hooks https://reactbyexample.github.io/hooks/