It all started with a tweet by Eric Elliott and everyone loses their mind.
一切始于埃里克·埃利奥特(Eric Elliott)的推文,每个人都失去了理智。
React has a setState() problem: Asking newbies to use setState() is a recipe for headaches. Advanced users have learned to avoid it. ;)
React有一个setState()问题:让新手使用setState()是头痛的秘诀。 高级用户已学会避免这种情况。 ;)
React introduced the concept of Hooks in 16.8 which allows us to hook into the lifecycle methods of a functional component. Not everyone is a fan of classes and FYI even a class boils down into a function in a javascript runtime environment. So using a class component just to take advantage of life cycle methods doesn’t makes sense to me.
React在16.8中引入了Hooks的概念,它使我们能够了解功能组件的生命周期方法。 并非所有人都喜欢类,因此仅供参考,甚至类都可以归结为javascript运行时环境中的函数。 因此,仅使用类组件来利用生命周期方法对我来说就没有意义。
Scope for this article will be familiarising with most used hooks by a hands on session to understand how we can replace a class component and its lifecycle methods with react hooks.
本文的范围将通过动手实践来熟悉最常用的钩子,以了解我们如何用react钩子替换类组件及其生命周期方法。
定义 (The Definition)
Hooks are functions that let us hook into the React state and lifecycle features from function components. Meaning that hooks allow us to easily manipulate the state of our functional component without needing to convert them into class components. Obviously hooks don’t work inside classes — they let you use React without classes.
挂钩是使我们能够从功能组件挂钩到 React状态和生命周期功能的函数。 这意味着钩子使我们能够轻松地操作功能组件的状态,而无需将其转换为类组件。 显然,钩子在类内部不起作用-它们使您可以在没有类的情况下使用React。
类会混淆人与机器 (Classes confuse both Humans and Machines)
![Morpheius telling the truth to NEO](https://i-blog.csdnimg.cn/blog_migrate/37c347c73ba3fd3392e1bb888da68d50.png)
We know that components and top-down data flow help us organise 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.
我们知道,组件和自上而下的数据流可帮助我们将大型UI组织成小的,独立的,可重用的片段。 但是,由于逻辑是有状态的并且不能提取到函数或其他组件,因此我们通常无法进一步分解复杂的组件。
- Huge components that are hard to refactor and test. 难以重构和测试的巨大组件。
- Duplicated logic between different components and lifecycle methods. 不同组件和生命周期方法之间的逻辑重复。
- Complex patterns like render props and higher-order components. 复杂的图案,例如渲染道具和高阶组件。
议程 (Agenda)
We will be developing a basic counter application just to keep it simple and bare minimum and we will try use hooks. Below is the already built counter using class component.
我们将开发一个基本的计数器应用程序,只是为了使其保持简单和最小化,我们将尝试使用钩子。 以下是使用类组件已构建的计数器。
From here on , i will be explaining more through code and less through writing.
从这里开始,我将通过代码解释更多而通过编写解释更少。
We will be covering the following hooks -
我们将介绍以下钩子-
useState — We will see different examples of using this hook including best practices, using multiple useStates etc.
useState —我们将看到使用此钩子的不同示例,包括最佳实践,使用多个useStates等。
useReducer — An alternative of useState. We will see how we can use this hook to manipulate state just like redux reducers do.
useReducer — useState的替代方法。 我们将看到如何像redux reducer一样使用这个钩子来操纵状态。
useEffect — The ultimate replacement of React class lifecycle methods. We will see how we can replace all those componentDid*#*# methods.
useEffect — React类生命周期方法的最终替代。 我们将看到如何替换所有这些componentDid *#*#方法。
useContext — A rather late addition to React’s context API.
useContext —对React的上下文API的较新添加。
简单使用状态 (Simple useState)
useState is “used” to manage state in functional component.
useState被“用来”管理功能组件中的状态。
Two highlights in this snippet:
此片段中的两个重点:
- we have a very simple function that uses useState and renders an h1 wrapped within a div. The counter is being displayed within the h1 tag. 我们有一个非常简单的函数,它使用useState并呈现包裹在div中的h1。 计数器显示在h1标签内。
- The output will be the value 0 displayed on the screen. 输出将是屏幕上显示的值0。
Lets dig a bit deeper..
让我们深入一点。
当我们调用useState()时会发生什么? (What happens when we call useState()?)
useState takes in an initial state as a parameter and returns an array with two objects — State value and an Updater function.
useState以初始状态作为参数,并返回带有两个对象的数组- 状态值和Updater函数。
State value is used within the component wherever we want and it doesn’t have to be an object unlike classes. The Updater function is used to update this state value. Instead of calling setState() method we call this updater function.
状态值可在组件中的任意位置使用,并且不必像类一样成为对象。 Updater函数用于更新此状态值。 而不是调用setState()方法,我们调用此更新程序函数。
![Image for post](https://i-blog.csdnimg.cn/blog_migrate/3ce735e3f2028c04c2c389b7e9ac3187.png)
Fact: Under the hood, react preserves this state value. So, when the component is rendered again because of state changes, the latest value is always available.
事实 :在后台,React保留该状态值。 因此,当由于状态更改而再次渲染组件时,最新值始终可用。
可能有多个useState? (Multiple useStates possible?)
Of course! We can create multiple state variables by using useState multiple times as shown below.
当然! 我们可以通过多次使用useState来创建多个状态变量,如下所示。
const [counter, setCounter] = useState(0);
const [error, setError] = useState('');
The counter variable is initialised to 0 and the error variable is initialised to empty string. It returns a pair of values: the current state and a function that updates it.
计数器变量初始化为0,错误变量初始化为空字符串。 它返回一对值:当前状态和更新状态的函数。
This is why we write const [counter, setCounter] = useState(0)
. This is similar to this.state.count
and this.setState
in a class, except you get them in a pair.
这就是为什么我们写const [counter, setCounter] = useState(0)
。 这与类中的this.state.count
和this.setState
相似,只不过它们成对存在。
那么方括号是什么意思? 有人吗 (So what Do Square Brackets Mean? Anybody?)
const [count, setState] = useState(0);
const [count, setState] = useState(0);
This JavaScript syntax is called “array de-structuring”. It means that we’re making two new variables count
and setState
, where count is set to the first value returned by useState
, and setState
of the second one is an updater function which can be used to modify the state (count in this case).
这种JavaScript语法称为“ 数组解构 ”。 这意味着我们要创建两个新变量count
和setState
,其中count设置为useState
返回的第一个值,第二个setState
是一个更新器函数,可用于修改状态(在这种情况下为count) 。
使用更新程序功能 (Using updater function)
To update the counter we can use the below code, anywhere, maybe on click of a button, etc. setCounter(counter+1);
要更新计数器,我们可以在任何地方使用以下代码,可能只需单击按钮等setCounter(counter+1);
We will add two buttons, to the page. One will increase the counter when clicked and the other will decrease the counter.
我们将在页面上添加两个按钮。 一个会在单击时增加计数器,而另一个会减少计数器。
We will write an increment() and decrement() helper function which will be respectively invoked from the button click.
我们将编写一个增量()和减量()辅助函数,分别通过单击按钮来调用。
Quick Question: Can we use
setCounter (counter ++)
instead ofsetCounter (counter + 1)
in the above code? Answer in comments.快速提问:可以在上面的代码中使用
setCounter (counter ++)
代替setCounter (counter + 1)
吗? 在评论中回答。
在更新程序功能中使用功能参数 (Using functional parameter in updater function)
setState actions are asynchronous and are batched for performance gains. setState()
does not immediately mutate this.state
but creates a pending state transition. Accessing this.state after calling this method can potentially return the existing value. There is no guarantee of synchronous operation of calls to setState and calls may be batched for performance gains.
setState操作是异步的,并且为提高性能而进行了批处理。 setState()
不会立即this.state
但会创建一个挂起的状态转换。 调用此方法后访问this.state可能会返回现有值。 无法保证对setState的调用的同步操作,并且可能为提高性能而对调用进行批处理。
setState alters the state and causes re-rendering. This can be an expensive operation and making it synchronous might leave the browser unresponsive.
setState更改状态并导致重新渲染。 这可能是一项昂贵的操作,并且使其同步可能会使浏览器无响应。
If you need to ensure ordering of events after a setState call is made, you can pass a callback function.this.setState({ something: true }, () => console.log(this.state))
如果需要确保在进行setState调用后事件的顺序,则可以传递一个回调函数。 this.setState({ something: true }, () => console.log(this.state))
为负数放置错误状态 (Putting error state for negative numbers)
Let’s add a feature to show error message, when the counter goes negative.Let’s create a variable to hold the error state.
让我们添加一个功能来在计数器变为负数时显示错误消息,让我们创建一个变量来保存错误状态。
const [error, setError] = useState(“”);```
Let’s modify the code to accommodate error state:
让我们修改代码以适应错误状态:
{ error.length > 0 && <div className="animated error"> {error} </div> }
Here we are checking for the presence of error and if true we display the message. Lastly, to make it work let’s modify the increment() and decrement() function like so.
在这里,我们正在检查是否存在错误,如果为true,则显示消息。 最后,为使其正常工作,让我们像这样修改increas()和decrement()函数。
useReducer挂钩 (useReducer Hook)
React now provides a built in useReducer hook, which can be used instead of the redux library. It accepts a reducer of type (state, action) => newState
, and returns the current state paired with a dispatch
method.
React现在提供了一个内置的useReducer钩子,可以使用它代替redux库。 它接受类型为(state, action) => newState
,并返回与dispatch
方法配对的当前状态。
Though useState and useReducer both achieve same things but there is a key difference — useState()
returns an updater function and useReducer
returns a dispatch function.
尽管useState和useReducer都可以实现相同的功能,但是有一个关键的区别useState()
返回一个updater函数,而useReducer
返回一个调度函数。
那么什么时候使用什么呢? (So when to use what?)
Usually in big(complex) applications the state that the containers or components maintain is not that simple. Containers maintain a huge state and they have to deal with with changing states based on previous states. Currently we do it within componentsWillReceiveProps()
and dispatch actions based on props and nextProps.
通常在大型(复杂)应用程序中,容器或组件所维护的状态不是那么简单。 容器保持着巨大的状态,因此必须根据以前的状态来处理变化的状态。 当前,我们在componentsWillReceiveProps()
执行此操作,并根据props和nextProps调度动作。
A typical use case will be when different analytics events needs to be fired based on different steps of a progressive form that has steps. It can really get complex like this.
典型的用例是需要根据具有步骤的渐进形式的不同步骤来触发不同的分析事件。 这样真的会变得复杂。
if (nextStep === STEP.ADD) {
current = nextStep;
} else if (isCalculator && currentStep !== STEP.ADD &&
currentStep !== STEP.SUCCESS &&
currentStep !== STEP.ERROR) {
current = STEP.SUBSTRACT;
} else if (nextStep && currentStep !== nextStep) {
current = nextStep;
} else if (!nextStep) {
current = currentStep;
}
switch (current) {
case STEP.ADD:
Analytics.emitEvent("ADD");
break;
case STEP.SUBSTRACT:
Analytics.emitEvent("SUBSTRACT");
break;
case STEP.MULTIPLY:
Analytics.emitEvent("MULTIPLY");
break;
default:
break;
}
![Image for post](https://i-blog.csdnimg.cn/blog_migrate/374eff461e3d19c3c5f84d719458e12b.png)
useReducer
is usually preferable in such scenarios when you have complex state logic that involves multiple sub-values.
在这种情况下,当您具有涉及多个子值的复杂状态逻辑时,通常首选useReducer
。
让我们定义我们的减速器 (Let’s define our reducer)
function reducer(state, action) {
switch (action.type) {
case "increment":
return {
...state,
counter: state.counter + 1
}
case "decrement":
return {
...state,
counter: state.counter - 1
}
case "reset":
return {
...state,
counter: Number(action.value)
}
default:
return state;
}
}
Assume our “complex” state to be this
假设我们的“复杂”状态是这样
const defaultState = {counter: 0, someothervalues: "test"};
const defaultState = {counter: 0, someothervalues: "test"};
Now, within our Counter component, create a reducer and initialise it.
现在,在我们的Counter组件中,创建一个reducer并将其初始化。
const [state, dispatch] = useReducer(reducer, defaultState);
const [state, dispatch] = useReducer(reducer, defaultState);
The increment decrement and reset JSX will change too like this:
递增递减和重置JSX也会像这样更改:
使用效果 (UseEffect)
![Image for post](https://i-blog.csdnimg.cn/blog_migrate/f1c3d810c47b6ae43eb6c35da9898726.png)
The useEffect hook allows us to hook into the lifecycle of the component, like componentDidMount
, componentDidUpdate
, componentWillUnmount
. So this one function plays the role of about three lifecycle events from class component.
useEffect挂钩允许我们挂钩到组件的生命周期,例如componentDidMount
, componentDidUpdate
, componentWillUnmount
。 因此,这一功能扮演着类组件中大约三个生命周期事件的角色。
The key ingredient of useEffect is the Dependency Array. This Dependency array decides how the useEffect behaves in response to the changes in the component props.
useEffect的关键要素是Dependency Array 。 这个Dependency数组决定useEffect如何响应组件props的变化。
useEffect(() => {
console.log(`RENDER: Count: ${count}`);
},<dependency array>);
We can provide as many props in the array on which we want the useEffect()
to make the component re-render. If any of the defined dependent componentsWillReceiveProps change, the component will re-render.
我们可以在数组中提供许多道具,以使useEffect()
使组件重新呈现。 如果任何已定义的从属组件WillReceiveProps发生更改,则组件将重新呈现。
Now this can have different possible possible forms —
现在,它可以具有不同的可能形式-
useEffect( () => {})
: Default state i.e. watch for every thing and re-render on every update (componentDidUpdate).useEffect( () => {})
:默认状态,即监视所有事物并在每次更新(componentDidUpdate)时重新呈现。useEffect( () => {}, [])
: No dependent props to watch for any change (componentDidMount).useEffect( () => {}, [])
:没有依赖的道具来监视任何更改(componentDidMount)。useEffect( () => () => {console.log("OverReact!!")}, [])
: Returning a function fromuseEffect()
(componentDidUnMount).useEffect( () => () => {console.log("OverReact!!")}, [])
:从useEffect()
(componentDidUnMount)返回一个函数。
UseEffect的影响 (The Affect of UseEffect)
By default the useEffect()
has no effect on the component i.e. it runs on every render. But playing with arguments of useEffect we could achieve what different class lifecycle methods can do in a React class without using one.
默认情况下, useEffect()
对组件没有影响,即它在每个渲染器上运行。 但是通过使用useEffect的参数,我们可以实现不同的类生命周期方法在React类中可以完成的工作而无需使用一个。
componentDidUpdate: Simply using useEffect with no dependents array allows us to achieve these lifecycle methods.
componentDidUpdate:只需将useEffect与不依赖数组一起使用,就可以实现这些生命周期方法。
componentDidMount: Typically this is required when you need to fetch some data or other operations when the component first mounts.
componentDidMount:通常,在首次安装组件时需要获取某些数据或其他操作时,这是必需的。
componentDidUnmount: The useEffect function can return an optional cleanup function. The useEffect here returns a callback function. Here you can do the necessary cleanup like removing eventhandlers, reference cleanup etc.
componentDidUnmount: useEffect函数可以返回可选的清除函数。 这里的useEffect返回一个回调函数。 在这里,您可以进行必要的清理,例如删除事件处理程序,引用清理等。
Fact: The hooks are asynchronous unlike their class counterparts. This means that they dont block the browser rendering and can happen in the background while browser is busy with its event loop. There are some hooks like
useLayoutEffect
which are synchronous which is obvious as the layout rendering is a blocking change the browser does when repainting happens.事实 :钩子是异步的,与类中的钩子不同。 这意味着它们不会阻止浏览器呈现,并且可以在浏览器忙于其事件循环时在后台发生。 有一些挂钩,例如
useLayoutEffect
,它们是同步的,这很明显,因为布局渲染是重绘发生时浏览器所做的一个阻塞更改。
![Image for post](https://i-blog.csdnimg.cn/blog_migrate/905f48c3c22bffcc3f35749d46134f76.png)
useContext API (useContext API)
Imagine that you have a deeply nested hierarchy of React components. Now, let’s say that the parent needs to pass data, not to its immediate child but to a grandchild or a great-grandchild. This is (in)famously called prop-drilling.
想象一下,您有一个嵌套的React组件层次结构。 现在,假设父级需要传递数据,而不是传递数据给直子,而是传递给孙子或曾孙。 这就是众所周知的“钻探”。
You would have to update the prop at each child component. Redundant, time-consuming, and error-prone!
您将必须在每个子组件上更新道具。 冗余,耗时且容易出错!
It is in scenarios like these that useContext
comes in handy.
在这种情况下, useContext
派上了用场。
![Image for post](https://i-blog.csdnimg.cn/blog_migrate/35313b242894c192ed086cfca4648777.png)
useContext
creates a “context” at the top level and allows it to be “used” anywhere in the hierarchy.
useContext
在顶层创建一个“上下文”,并允许其在层次结构中的任何位置“使用”。
This context or object/value is available to all components in the hierarchy following that top-level component. This happens without having to pass any props down the hierarchy explicitly.
该上下文或对象/值可用于该顶级组件之后的层次结构中的所有组件。 发生这种情况时,无需显式传递层次结构中的任何支持。
使用useContext启动并运行 (Up and running with useContext)
There are three major things to remember to get using the context API
使用上下文API时要记住三件事
Import useContext hook from react lib wherever you want to create a context:
无论何时要创建上下文,都可以从react lib导入useContext钩子:
import {useContext} from React;
import {useContext} from React;
React.createContext to create a context that will be sharable amongst the components:
React.createContext创建一个可以在组件之间共享的上下文:
const ThemeContext = React.createContext();
const ThemeContext = React.createContext();
Provider to provide this context down the line (child components): We will create a component
<ThemeProvider>
that will create a context and pass it down to its children.提供程序以提供此上下文(子组件):我们将创建一个组件
<ThemeProvider>
,该组件将创建上下文并将其传递给其子组件。
const ThemeProvider = ({
children
}) => {
const [theme, setTheme] = useState("theme1");function handleChange(e) {
setTheme(e.target.value);
e.preventDefault();
}
return ( <
ThemeContext.Provider value = {
theme
} >
<
select onChange = {
handleChange
} >
<
option value = "theme1" > Theme 1 < /option> <
option value = "theme2" > Theme 2 < /option> <
/select> {
children
} <
/ThemeContext.Provider>
);
}
Few things to note about the bold line:
关于粗体字的几点注意事项:
First we return JSX from this component that is wrapped in the provider. A provider is something that provides a data/value to this component and its underlying hierarchy.
首先,我们从包装在提供程序中的该组件中返回JSX。 提供程序是为该组件及其基础层次结构提供数据/值的东西。
If you have worked with redux stores, we wrap the entire <App> component in a provider and Provide the redux store to it so this store is now available to all the hierarchy through the reducers involved.
如果您使用过Redux存储,则将整个<App>组件包装在提供程序中,并为其提供Redux存储,这样该存储现在可通过所涉及的reducer用于所有层次结构。
Similarly, here we create a context and wrap our root component in this provider and supply the value/data/object to be accessed by its children directly.
同样,在这里我们创建一个上下文,并将根组件包装在此提供程序中,并提供要由其子级直接访问的值/数据/对象。
The all new <Counter>
Component:
全新的<Counter>
组件:
function Counter() {
const [state, setState] = useReducer(reducer, defaultState);
const [value, reset] = useState(0);
const theme = useContext(ThemeContext); function handleChange(e) {
reset(e.target.value);
} return (
<div className={`app ${theme}`}>
<h1>useContext </h1>
<h1>{state.counter}</h1>
<input style={{width:"20%", textAlign:"center"}} type="number" value={value} onChange={handleChange} placeholder="enter initial counter value and press reset"/>
<div className="buttons">
<button onClick={()=> setState({counter: state.counter+1})}>+
</button>
<button onClick={()=> setState({counter: state.counter-1})}>-
</button>
<button onClick={()=> setState({counter: Number(value)})}>
reset
</button>
</div>
</div>
);
}
The above piece of code grabs the theme from the context.And we use this theme as part of the jsx
上面的代码从上下文中获取了主题,我们将该主题用作jsx的一部分
The <App>
Component
<App>
组件
function App() {
return (
<ThemeProvider>
<Counter />
<Footer/>
</ThemeProvider>
);
}
const rootElement = document.querySelector("#root"); ReactDOM.render(<App />, rootElement);
Here is the full code:
这是完整的代码:
![Image for post](https://i-blog.csdnimg.cn/blog_migrate/ec274437afb57e8a7e8804c5b32fa386.png)
翻译自: https://medium.com/@devansh151/overreact-with-hooks-a-hands-on-session-275b94305f0b