In this tutorial we will be going over how to integrate React Hooks into a React Redux project without changing the Redux code (reducers and actions) at all.
在本教程中,我们将讨论如何将React Hooks集成到React Redux项目中,而根本不更改Redux代码(缩减器和操作)。
To save time, we can start with with a basic React Redux app instead of building one from scratch. This will allow you to see the before and after code side by side and make integration for your app much easier.
为了节省时间,我们可以从基本的React Redux应用开始,而不是从头开始构建一个。 这将使您可以并排查看代码的前后,并使应用程序集成更加容易。
You can also follow me on twitter for more tutorials in the future: here
您也可以在Twitter上关注我,以获取将来的更多教程: 此处
Starter code:
入门代码:
iqbal125/modern-react-app-sampleContribute to iqbal125/modern-react-app-sample development by creating an account on GitHub.github.com
iqbal125 / modern-react-app-sample 通过在GitHub上创建帐户来为iqbal125 / modern-react-app-sample开发做出贡献。 github.com
使用正确版本的React (Using the correct Version of React)
The very first thing we have to do is make sure we have the correct version of React. At the time of this writing, create-react-app does not give you the correct version. So what you can do is use create-react-app then go into your package.json and type in the correct version. So just change React and React-dom to version 16.8. Save the file and delete your node modules folder. Run npm install and you are good to go.
我们要做的第一件事就是确保我们具有正确版本的React。 在撰写本文时,create-react-app没有为您提供正确的版本。 因此,您可以使用create-react-app然后进入package.json并输入正确的版本。 因此,只需将React和React-dom更改为版本16.8。 保存文件并删除您的节点模块文件夹。 运行npm install,一切顺利。
{
"name": "app2",
"version": "0.1.0",
"private": true,
"dependencies": {
"auth0-js": "^9.8.2",
"history": "^4.7.2",
"react": "^16.8.0",
"react-dom": "^16.8.0",
"react-redux": "^6.0.0",
"react-router": "^4.3.1",
"react-router-dom": "^4.3.1",
"react-scripts": "2.1.1",
"redux": "^4.0.1"
},
将React类重构为React Hook (Refactoring a React class to a React Hook)
So the first thing we will do is refactor a React class component to a React Hook. Let’s open our App.js file and turn it into a Hook, so refactor your App.js to the following:
因此,我们要做的第一件事是将React类组件重构为React Hook。 让我们打开App.js文件并将其转换为一个Hook,因此将App.js重构为以下内容:
import React, { Component } from 'react';
import Routes from './routes';
const App = () => {
return(
<div>
React
<Routes />
</div>
)
}
export default App;
So basically just turn the class into an arrow function and delete the render method. And that’s it, you have now created a React Hook!
因此,基本上只需将类转换为箭头函数并删除render方法。 就是这样,您现在已经创建了一个React Hook!
设置另一个挂钩 (Setting up another Hook)
In the same way, we can setup up another Hook, which we will setup in a folder called Hooks.
以同样的方式,我们可以设置另一个Hook,并将其安装在名为Hooks的文件夹中。
So create a hooks_container.js file in the hooks directory and set it up like so:
因此,在hooks目录中创建一个hooks_container.js文件,并将其设置如下:
import React, { useState } from 'react';
const HooksContainer = () => {
return(
<div>
</div>
)
}
export default HooksContainer;
useState()挂钩 (The useState() Hook)
We will now begin to set up some basic non-global component state with the useState() hook.
现在,我们将开始使用useState()钩子设置一些基本的非全局组件状态。
The useState() hook is similar to the React setState() function. It is setup with array destructuring, where the first element in the array is the state value and the second element is a function to change the state.
useState()钩子类似于React setState()函数。 它是通过数组解构设置的,其中数组中的第一个元素是状态值,第二个元素是更改状态的函数。
Let’s just create basic increment and decrement buttons to see how the use state function.
让我们只创建基本的增量和减量按钮,以了解使用状态函数的方式。
Set up the buttons like so:
像这样设置按钮:
import React, { useState } from 'react';
const HooksContainer = () => {
const [value, setValue] = useState(0)
const incrementValue = () => {
setValue(value + 1 )
}
const decrementValue = () => {
setValue(value - 1 )
}
return(
<div>
<button onClick={() => incrementValue()}> Add Local Value </button>
<button onClick={() => decrementValue()}> Dec Local Value </button>
<br />
<div>
Local React State: {value}
</div>
</div>
)
}
export default HooksContainer;
Notice we don’t have to use the “props” or “state” keyword anywhere we can just use the variable and function name directly. This is one of the things that makes React Hooks so easy to work with.
注意,我们不必在任何可以直接使用变量和函数名称的地方使用“ props”或“ state”关键字。 这是使React Hooks如此易于使用的原因之一。
Your app should look something like this.
您的应用应该看起来像这样。
And you should be able to freely increase or decrease the number.
并且您应该能够自由增加或减少数量。
Now that we have a basic idea of how useState() works we can move onto something a little more complex.
现在,我们对useState()的工作原理有了一个基本的了解,我们可以继续进行一些复杂的工作。
useReducer()挂钩 (useReducer() hook)
We can now begin setting up the useReducer() hook.
现在我们可以开始设置useReducer()挂钩。
Before we can use the useReducer() hook we must first setup the reducer. The actions can actually be left as is. And the change we have to make to the reducer is very minimal. All we have to do is change the export statements instead of exporting default. We have to export both the reducer and the initial state.
在使用useReducer()挂钩之前,我们必须先设置reducer。 实际上可以保留动作。 我们对减速器所做的更改非常小。 我们要做的就是更改导出语句,而不是导出默认值。 我们必须导出reducer和初始状态。
To save time, just create a new reducer called hooks_reducer.js in the reducer file and copy the code from Reducer1. You should have something that looks like this:
为了节省时间,只需在化简器文件中创建一个名为hooks_reducer.js的新化简器,然后从Reducer1复制代码。 您应该具有以下外观:
import * as ACTION_TYPES from '../actions/action_types'
export const initialState = {
stateprop1: false,
}
export const HooksReducer1 = (state = initialState, action) => {
switch(action.type) {
case ACTION_TYPES.SUCCESS:
return {
...state,
stateprop1: true
}
case ACTION_TYPES.FAILURE:
return {
...state,
stateprop1: false
}
default:
return state
}
}
Now simply import this reducer and its initial state to the hooks_container.js. And pass them both in to the useReducer() hook.
现在,只需将此减速器及其初始状态导入hooks_container.js。 并将它们都传递给useReducer()挂钩。
import * as HooksReducer1 from '../store/reducers/hooks_reducer';
...
const [state, dispatch] = useReducer(HooksReducer1.HooksReducer1, HooksReducer1.initialState)
Let’s also create 2 buttons to change stateprop1 from false to true and then false again. And we can also create a ternary expression to display text depending on whether stateprop1 is true or false. Remember that stateprop1 is the same one we set up in the HookReducer1, but we are updating here in our container.
我们还创建2个按钮,将stateprop1从false更改为true,然后再次更改为false。 而且,我们还可以创建一个三元表达式来显示文本,具体取决于stateprop1是true还是false。 请记住,stateprop1与我们在HookReducer1中设置的状态相同,但是我们在这里在容器中进行更新。
And we are using the same pre-existing actions to update the reducer. Notice in the comments I left two alternate methods of dispatching actions. They are all doing the same thing. Returning a javascript object with a type of a string of SUCCESS.
我们正在使用相同的预先存在的操作来更新reducer。 请注意,在注释中,我留下了两种其他的分派操作方法。 他们都在做同一件事。 返回类型为SUCCESS的javascript对象。
So your code should look like this:
因此,您的代码应如下所示:
import React, { useState } from 'react';
import * as ACTIONS from '../store/actions/actions';
import * as HooksReducer1 from '../store/hooks_state/reducer1_hooks';
const HooksContainer = () => {
const [state, dispatch] = useReducer(HooksReducer1.HooksReducer1, HooksReducer1.initialState)
const [value, setValue] = useState(0)
const incrementValue = () => {
setValue(value + 1 )
}
const decrementValue = () => {
setValue(value - 1 )
}
const handleDispatchTrue = () => {
// dispatch(type: "SUCCESS")
// dispatch(ACTIONS.SUCCESS)
dispatch(ACTIONS.success())
}
const handleDispatchFalse = () => {
// dispatch(type: "FAILURE")
// dispatch(ACTIONS.FAILURE)
dispatch(ACTIONS.failure())
}
return(
<div>
<button onClick={() => incrementValue()}> Add Local Value </button>
<button onClick={() => decrementValue()}> Dec Local Value </button>
<button onClick={() => handleDispatchTrue()}>Dispatch True </button>
<button onClick={() => handleDispatchFalse()}>Dispatch False </button>
<br />
<br />
<div>
Local React State: {value}
</div>
<div>
{state.stateprop1
? <p> stateprop1 is true </p>
: <p> stateprop1 is false </p>
}
</div>
</div>
)
}
export default HooksContainer;
Your app should look like this and you should be able to change stateprop1 from the hooks container:
您的应用程序应该看起来像这样,并且您应该能够从hooks容器中更改stateprop1:
You will notice one problem when we go to another component: the state is not saved. This is because even though we are using actions and reducers, the state is still local component state and not available globally. To make the state available globally we actually have to use the React Context, which we will setup next few sections.
当我们转到另一个组件时,您会注意到一个问题:状态未保存。 这是因为即使我们使用动作和缩减器,该状态仍然是本地组件状态,并且无法全局使用。 为了使状态全局可用,我们实际上必须使用React Context,我们将在接下来的几节中进行设置。
设置动作和减速器 (Setting up Actions and the Reducer)
Before we setup Context, let’s setup the Actions and Reducer we will use with it. So let’s add a second property to the HooksReducer1 called stateprop2 and set it to 0.
在设置上下文之前,让我们设置将要使用的Actions和Reducer。 因此,让我们向HooksReducer1添加另一个名为stateprop2的属性并将其设置为0。
We will now need to set up actions and action types to work with this new piece of state.
现在,我们需要设置动作和动作类型来处理这种新状态。
First let’s create 2 action types for stateprop2:
首先让我们为stateprop2创建2种动作类型:
export const INC_GLOBAL_STATE = "INC_GLOBAL_STATE"
export const DEC_GLOBAL_STATE = "DEC_GLOBAL_STATE"
Then we can go in our actions file and create 2 action creators to handle these actions types.
然后,我们可以进入动作文件并创建2个动作创建者来处理这些动作类型。
export const inc_global_state = () => {
return {
type: ACTION_TYPES.INC_GLOBAL_STATE
}
}
export const dec_global_state = () => {
return {
type: ACTION_TYPES.DEC_GLOBAL_STATE
}
}
Finally we need to setup our reducer which should look like this:
最后,我们需要设置减速器,该减速器应如下所示:
import * as ACTION_TYPES from '../actions/action_types'
export const initialState = {
stateprop1: false,
stateprop2: 0
}
export const HooksReducer1 = (state = initialState, action) => {
switch(action.type) {
case ACTION_TYPES.SUCCESS:
return {
...state,
stateprop1: true
}
case ACTION_TYPES.FAILURE:
return {
...state,
stateprop1: false
}
case ACTION_TYPES.INC_GLOBAL_STATE:
return {
...state,
stateprop2: state.stateprop2 + 1
}
case ACTION_TYPES.DEC_GLOBAL_STATE:
return {
...state,
stateprop2: state.stateprop2 - 1
}
default:
return state
}
}
React上下文 (React Context)
Next, we have to set up the context object. Simply create another context.js file and setup it up like so:
接下来,我们必须设置上下文对象。 只需创建另一个context.js文件并按如下所示进行设置即可:
import React from 'react';
const Context = React.createContext({
prop1: false
})
export default Context;
Note that prop1 here is irrelevant. We will be overriding this in our App.js file. We simply supplied prop1 to initialize the Context object. All the code for updating and reading our state will be done in the App.js file.
请注意,此处的prop1是无关的。 我们将在App.js文件中覆盖此内容。 我们只是提供了prop1来初始化Context对象。 用于更新和读取状态的所有代码将在App.js文件中完成。
Next let’s import this context object to our App.js file. Also import HooksReducer1 and the Actions since we will use them here.
接下来,将这个上下文对象导入到我们的App.js文件中。 还要导入HooksReducer1和操作,因为我们将在这里使用它们。
Let’s also setup the useReducer the same way as before.
让我们也像以前一样设置useReducer。
import React, { useReducer } from 'react';
import Routes from './routes';
import Context from './utils/context';
import * as ACTIONS from './store/actions/actions';
import * as HooksReducer1 from './store/reducers/hooks_reducer';
const App = () => {
const [valueGlobal, dispatchActionsGlobal] = useReducer(HooksReducer1.HooksReducer1, HooksReducer1.initialState)
...
Next we need to create 2 functions to dispatch our action creators we just created. These functions will increment and decrement stateprop2.
接下来,我们需要创建2个函数来分派刚创建的动作创建者。 这些函数将递增和递减stateprop2。
Also we need to wrap our routes with a <Context.Provider /> component. This is what allows us to have a global state. The <Context.Provider /> component passes down all the state to the child components. Since App.js is the root component the state is passed down to every component in the app, which is what makes the state global.
另外,我们需要使用<Context.Provider />组件包装路由。 这就是使我们拥有全球状态的原因。 <Context.Provider />组件将所有状态传递给子组件。 由于App.js是根组件,因此状态会向下传递到应用程序中的每个组件,这就是使状态成为全局状态的原因。
The state itself is contained in a prop called “value”. All this is similar to the <Provider /> component and “store” prop seen in React-Redux.
状态本身包含在称为“值”的属性中。 所有这些都类似于React-Redux中的<Provider />组件和“ store”道具。
We then need to pass in the state and action dispatches as properties to the value prop. We will need 3 properties here: one for a function to increment our state value, one for a function to decrement our state value and one to hold the actual state value.
然后,我们需要将状态和动作分派作为属性传递给值prop。 这里我们将需要3个属性:一个用于增加状态值的函数,一个用于减少状态值的函数,以及一个用于保存实际状态值的属性。
All together your App.js file will look like this:
您的App.js文件将全部如下所示:
import React, { useReducer } from 'react';
import Routes from './routes';
import Context from './utils/context';
import * as ACTIONS from './store/actions/actions';
import * as HooksReducer1 from './store/reducers/hooks_reducer';
const App = () => {
const [valueGlobal, dispatchActionsGlobal] = useReducer(HooksReducer1.HooksReducer1, HooksReducer1.initialState)
const incrementGlobalValue = () => {
dispatchActionsGlobal(ACTIONS.inc_global_state() )
}
const decrementGlobalValue = () => {
dispatchActionsGlobal(ACTIONS.dec_global_state() )
}
return(
<div>
React
<Context.Provider
value={{
valueGlobalState: valueGlobal,
addGlobalValue: () => incrementGlobalValue(),
decGlobalValue: () => decrementGlobalValue()
}}>
<Routes />
</Context.Provider>
</div>
)
}
export default App;
I have intentionally kept all the function and property names different so it will be easier to see where everything is coming from when we use Context in the child component.
我故意使所有函数和属性名称保持不同,因此当我们在子组件中使用Context时,将更容易看到所有内容来自何处。
So now, all these properties defined in the value prop can be accessed by all the child components, and we therefore have a global state!
因此,现在,值prop中定义的所有这些属性都可以被所有子组件访问,因此我们具有全局状态!
通过useContext()挂钩在子组件中使用Context。 (Using Context in a child component with the useContext() hook.)
Let’s go back to our hooks container and use these functions and state we just setup.
让我们回到我们的hooks容器并使用这些功能并声明我们刚刚设置的状态。
To use the Context in our hooks container, we first need to import it and pass the entire Context object into the useContext hooks. Like so:
要在hooks容器中使用Context,我们首先需要将其导入并将整个Context对象传递到useContext钩子中。 像这样:
...
import Context from '../utils/context';
const HooksContainer = () => {
const context = useContext(Context)
...
Next we can directly access the properties we set in the value prop directly through the context variable.
接下来,我们可以直接通过上下文变量直接访问在值prop中设置的属性。
...
<button onClick={() => context.addGlobalValue()}> Add Global Value </button>
<button onClick={() => context.decGlobalValue()}> Dec Global Value </button>
...
Remember addGlobalValue() is the name of the property we supplied to the value prop in App.js. It is not the name of the function for dispatching actions or the name of the function we set in the useReducer() hook in App.js.
请记住,addGlobalValue()是我们提供给App.js中的value道具的属性的名称。 它不是用于分派操作的函数的名称,也不是我们在App.js的useReducer()挂钩中设置的函数的名称。
Accessing the state value through Context is done in the following way:
通过Context访问状态值的方式如下:
...
<p>Global Value: {context.valueGlobalState.stateprop2}</p>
...
And similar to dispatching actions, the valueGlobalState is the property name supplied to the value prop. And we have to access stateprop2 with dot notation from the valueGlobalState property, since valueGlobalState contains the entire intialState from HooksReducer1, including stateprop1.
与分派操作类似,valueGlobalState是提供给值prop的属性名称。 而且,由于valueGlobalState包含HooksReducer1中的整个intialState,包括stateprop1,因此必须从valueGlobalState属性中使用点表示法访问stateprop2。
And if you test now you will see that the state updates and persists even after you go to another component, allowing you replicate Redux functionality and have a global state.
而且,如果您现在进行测试,您将看到状态即使在转到另一个组件后仍会更新并保持不变,从而使您可以复制Redux功能并具有全局状态。
You can use this pattern to essentially scale this up for all your Redux code.
您可以使用此模式从根本上扩展所有Redux代码。
final code:
最终代码:
iqbal125/react-hooks-basicContribute to iqbal125/react-hooks-basic development by creating an account on GitHub.github.com
iqbal125 / react-hooks-basic 通过在GitHub上创建帐户来为iqbal125 / react-hooks-basic开发做出贡献。 github.com
摘要 (Summary)
So here is a conceptual summary of how to do it (requires basic React hooks knowledge ):
因此,这是如何做到这一点的概念性总结(需要基本的React hooks知识):
Actions do not need to be changed at all. Reducers do not need to be changed either. Simply export both the initial state and the reducer instead of just the reducer. Do not use “export default” at the bottom of the reducer file.
根本不需要更改操作。 减速器也不需要更换。 只需导出初始状态和化简器,而不只是导出化简器。 不要在化简文件的底部使用“导出默认值”。
Import the reducer and its initial state to the root App.js file. Call the useReducer() hook in the root App.js file and save it in a variable. Similar to the useState hook, the first element in the array destructuring is the state value and the second element is the function to change the state. Then Pass in both the reducer and initialState you imported to the useReducer() hook. Import as many reducers as you want and pass each of them into a separate useReducer() Hook.
将reducer及其初始状态导入到根App.js文件。 在根App.js文件中调用useReducer()挂钩,并将其保存在变量中。 与useState挂钩类似,数组解构中的第一个元素是状态值,第二个元素是更改状态的函数。 然后,将导入的reducer和initialState都传递给useReducer()挂钩。 导入任意数量的reduce,并将每个reduce传递到单独的useReducer()Hook中。
Import actions to App.js as normal. Dispatching actions is also exactly the same. Instead of using the mapDispatchToProps() function you will dispatch the actions from the change state function (second element in array destructuring) from the useReducer() hook call.
正常将动作导入App.js。 调度动作也完全相同。 代替使用mapDispatchToProps()函数,您将通过useReducer()挂钩调用从更改状态函数(数组解构中的第二个元素)分派动作。
Setup and initialize the React.CreateContext() function in a another file and import it to App.js. Then Wrap your <Routes /> with <Context.Provider>. You will generally need 3 properties for each piece of state for the value prop in the provider. 1 property to set the state to a new value, 1 to pass in the actual state, and 1 to set the state back to default.
在另一个文件中设置并初始化React.CreateContext()函数,然后将其导入App.js。 然后用<Context.Provider>包装<Routes />。 对于提供程序中的value prop,每个状态通常需要3个属性。 1个属性可将状态设置为新值,1个属性可传递到实际状态,1个属性可将状态恢复为默认值。
Then to use the state in the components, you first import the Context Object from context.js and then just pass it in to the useContext() hook and save this in a variable called “context” or whatever you like. Then to access the state property, just do the variable name “context” “.” then the name of property set in the value prop, followed by the name of the property set in the initialState of the reducer. To dispatch actions just do “context” “.” then call the property name.
然后,要使用组件中的状态,您首先要从context.js导入Context Object,然后将其传递到useContext()钩子中,然后将其保存在名为“ context”的变量中或任何您喜欢的变量中。 然后要访问state属性,只需执行变量名“ context”“。”即可。 然后是在属性prop中设置的属性名称,然后是在reducer的initialState中设置的属性名称。 要调度动作,只需执行“上下文”“。” 然后调用属性名称。
Once this is done your context state is available globally and will work with your existing React Redux code.
完成后,您的上下文状态将全局可用,并将与您现有的React Redux代码一起使用。
For a 100% Free Video version of this tutorial and more in-dept React Hooks content please see my Udemy course or Youtube playlist:
要获得本教程的100%免费视频版本以及更深入的React Hooks内容,请参阅我的Udemy课程或Youtube播放列表:
https://www.udemy.com/react-hooks-with-react-redux-migration
https://www.udemy.com/react-hooks-with-react-redux-migration
https://www.youtube.com/watch?v=l8ODM-KoDpA&list=PLMc67XEAt-ywplHhDpoj5vakceZNr8S0B
https://www.youtube.com/watch?v=l8ODM-KoDpA&list=PLMc67XEAt-ywplHhDpoj5vakceZNr8S0B