When you're learning React, you will almost always hear people say how great Redux is and that you should give it a try. The React ecosystem is growing at a swift pace, and there are so many libraries that you can hook up with React, such as flow, redux, middlewares, mobx, etc.
Learning React is easy, but getting used to the entire React ecosystem takes time. This tutorial is an introduction to one of the integral components of the React ecosystem—Redux.
Here are some of the commonly used terminologies that you may not be familiar with, but they are not specific to Redux per se. You can skim through this section and come back here when/if something doesn't make sense.
A pure function is just a normal function with two additional constraints that it has to satisfy:
For instance, here is a pure function that returns the sum of two numbers.
/* Pure add function */<br>const add = (x,y) => {<br> return x y;<br>}<br> <br>console.log(add(2,3)) //5<br><br>
Pure functions give a predictable output and are deterministic. A function becomes impure when it performs anything other than calculating its return value.
For instance, the add function below uses a global state to calculate its output. In addition, the function also logs the value to the console, which is considered to be a side effect.
const y = 10;<br><br>const impureAdd = (x) => {<br> console.log(`The inputs are ${x} and ${y}`);<br> return x y;<br>}<br>
"Observable side effects" is a fancy term for interactions made by a function with the outside world. If a function tries to write a value into a variable that exists outside the function or tries to call an external method, then you can safely call these things side effects.
However, if a pure function calls another pure function, then the function can be treated as pure. Here are some of the common side effects:
Splitting the component architecture into two is useful while working with React applications. You can broadly classify them into two categories: container components and presentational components. They are also popularly known as smart and dumb components.
The container component is concerned with how things work, whereas presentational components are concerned with how things look. To understand the concepts better, I've covered that in another tutorial: Container vs. Presentational Components in React.
A mutable object can be defined as follows:
A mutable object is an object whose state can be modified after it is created.
Immutability is the exact opposite—an immutable object is an object whose state cannot be modified after it is created. In JavaScript, strings and numbers are immutable, but objects and arrays are not. The example demonstrates the difference better.
/*Strings and numbers are immutable */<br><br>let a = 10;<br><br>let b = a;<br><br>b = 3;<br><br>console.log(`a = ${a} and b = ${b} `); //a = 10 and b = 3 <br><br>/* But objects and arrays are not */<br><br>/*Let's start with objects */<br><br>let user = {<br> name: "Bob",<br> age: 22,<br> job: "None"<br>}<br><br>active_user = user;<br><br>active_user.name = "Tim";<br><br>//Both the objects have the same value<br>console.log(active_user); // {"name":"Tim","age":22,"job":"None"} <br><br>console.log(user); // {"name":"Tim","age":22,"job":"None"} <br><br>/* Now for arrays */<br><br>let usersId = [1,2,3,4,5]<br><br>let usersIdDup = usersId;<br><br>usersIdDup.pop();<br><br>console.log(usersIdDup); //[1,2,3,4]<br>console.log(usersId); //[1,2,3,4]<br>
To make objects immutable, use the Store.getState()—To access the current state tree of your application.
Let's create a store. Redux has a configureStore
method to create a new store. You need to pass it a reducer, although we don't know what that is. So I will just create a function called reducer. You may optionally specify a second argument that sets the initial state of the store.
import { configureStore } from "redux";<br>// This is the reducer<br>const reducer = () => {<br>/*Something goes here */<br>}<br><br>//initialState is optional.<br>//For this demo, I am using a counter, but usually state is an object<br>const initialState = 0<br>const store = configureStore(reducer, initialState);<br>
Now we're going to listen to any changes in the store, and then console.log()
the current state of the store.
store.subscribe( () => {<br> console.log("State has changed" store.getState());<br>})<br>
So how do we update the store? Redux has something called actions that make this happen.
Actions are also plain JavaScript objects that send information from your application to the store. If you have a very simple counter with an increment button, pressing it will result in an action being triggered that looks like this:
{<br> type: "INCREMENT",<br> payload: 1<br>}<br>
They are the only source of information to the store. The state of the store changes only in response to an action. Each action should have a type property that describes what the action object intends to do. Other than that, the structure of the action is completely up to you. However, keep your action small because an action represents the minimum amount of information required to transform the application state.
For instance, in the example above, the type property is set to "INCREMENT", and an additional payload property is included. You could rename the payload property to something more meaningful or, in our case, omit it entirely. You can dispatch an action to the store like this.
store.dispatch({type: "INCREMENT", payload: 1});<br>
While coding Redux, you won't normally use actions directly. Instead, you will be calling functions that return actions, and these functions are popularly known as action creators. Here is the action creator for the increment action that we discussed earlier.
const incrementCount = (count) => {<br> return {<br> type: "INCREMENT",<br> payload: count<br> }<br>}<br>
So, to update the state of the counter, you will need to dispatch the incrementCount
action like this:
store.dispatch(incrementCount(1));<br>store.dispatch(incrementCount(1));<br>store.dispatch(incrementCount(1));<br>
If you head to the browser console, you will see that it's working, partially. We get undefined because we haven't yet defined the reducer.
So now we have covered actions and the store. However, we need a mechanism to convert the information provided by the action and transform the state of the store. Reducers serve this purpose.
An action describes the problem, and the reducer is responsible for solving the problem. In the earlier example, the incrementCount
method returned an action that supplied information about the type of change that we wanted to make to the state. The reducer uses this information to actually update the state. There's a big point highlighted in the docs that you should always remember while using Redux:
Given the same arguments, a Reducer should calculate the next state and return it. No surprises. No side effects. No API calls. No mutations. Just a calculation.
What this means is that a reducer should be a pure function. Given a set of inputs, it should always return the same output. Beyond that, it shouldn't do anything more. Also, a reducer is not the place for side effects such as making AJAX calls or fetching data from the API.
Let's fill in the reducer for our counter.
// This is the reducer<br><br>const reducer = (state = initialState, action) => {<br> switch (action.type) {<br> case "INCREMENT":<br> return state action.payload<br> default:<br> return state<br> }<br>}<br>
The reducer accepts two arguments—state and action—and it returns a new state.
(previousState, action) => newState<br>
The state accepts a default value, the initialState
, which will be used only if the value of the state is undefined. Otherwise, the actual value of the state will be retained. We use the switch statement to select the right action. Refresh the browser, and everything works as expected.
Let's add a case for DECREMENT
, without which the counter is incomplete.
// This is the reducer<br><br>const reducer = (state = initialState, action) => {<br> switch (action.type) {<br> case "INCREMENT":<br> return state action.payload<br> case "DECREMENT":<br> return state - action.payload<br> default:<br> return state<br> }<br>}<br>
Here's the action creator.
const decrementCount = (count) => {<br> return {<br> type: "DECREMENT",<br> payload: count<br> }<br>}<br>
Finally, dispatch it to the store.
store.dispatch(incrementCount(4)); //4<br>store.dispatch(decrementCount(2)); //2<br>
That's it!
This tutorial was meant to be a starting point for managing state with Redux. We've covered everything essential needed to understand the basic Redux concepts such as the store, actions, and reducers. Towards the end of the tutorial, we also created a working redux demo counter. Although it wasn't much, we learned how all the pieces of the puzzle fit together.
Over the last couple of years, React has grown in popularity. In fact, we have a number of items in the marketplace that are available for purchase, review, implementation, and so on. If you’re looking for additional resources around React, don’t hesitate to check them out.
In the next tutorial, we will make use of the things we've learned here to create a React application using Redux. Stay tuned until then. Share your thoughts in the comments.
The above is the detailed content of Getting Started With Redux: Why Redux?. For more information, please follow other related articles on the PHP Chinese website!