preface

Hello everyone, I am the front end secret book for NPY, to announce a thing to you, in the future, the younger brother will be in front of the river’s lake with Yin frame flower name, the recent business demand is too much, no time to write. Forgive me.

For many years, front-end engineers have endured humiliation, selling white fans and earning money to buy cabbages. They have been at the bottom of the chain of programmers’ contempt. Therefore, Danniu has moved the back-end MVC development thinking to the front end, and unified management of all actions and states in the application, so that everything can be followed by evidence

Story synopsis

Redux is a JavaScript application for managing data state and UI state. With the increasing complexity of JavaScript single page application (SPA) development, JavaScript needs to manage more states than ever before, and Redux makes it easier to manage. (Redux supports React, Angular, jQuery, and even pure JavaScript.) It is small (only 2KB, including dependencies) and has a very strong plugin extension ecosystem.As you can see from the figure, passing state without Redux is cumbersome. In Redux, data can be stored in a data warehouse (store-common state storage space), where state can be centrally managed, and which components use stroe to find the state. If the purple component en route wants to change its state, it just needs tostoreAnd then other components will automatically change with the.

First let’s take a look at the redux workflow:

Redux workflow

The React Components are like loan Creators. So we went to the library and got a book, and the first person we met was Action Creators, the librarian, and we said, “I need a Copy of Learn React From The Top down.” The librarian goes back to the Store and asks the Reducer to check if the Top-down Learn React is still there and if so, take it out and lend it to the Reducer.

There are four parts to the Redux workflow, the most important of which is the Store, because it manages all the data in the Store.

Store

  • The first step is to distinguish between store and state

A store is the state of an application, and is essentially a common object

Here’s a chestnut:

For example, we have a program that includes a counter and a to-do list

Then we can design the corresponding storage data structure for the application (application initial state) :

{
  counter: 0.todos: []}Copy the code

Store is the manager of application state and contains the following four functions:

  • GetState () # get the entire state
  • Dispatch (Action) # โ€ป Only way to trigger state change โ€ป
  • Subscribe (Listener) # you can think of as addEventListener in the DOM
  • ReplaceReducer (nextReducer) # generic for Webpack code-splitting when loading on demand

State === store.getState()

Redux states that an app should only have a single store that manages the unique state of the app. Redux also states that you cannot directly change the state of the app. In other words, the following actions are not allowed:

var state = store.getState()
state.counter = state.counter + 1 // Forbid changing state directly in business logic
Copy the code

If you want to changestateThat must bedispatchaactionThis is the only way to change the application state

Now you just need to remember that the action is just a normal object with a type attribute such as {type: ‘INCREMENT’}

State is obtained from store.getState(). To generate a store, we need to call Redux’s createStore:

import { createStore } from 'redux'.const store = createStore(reducer, initialState) // Store was created by reducer.
For now you just need to remember that reducer is a function that updates and returns a new state
Copy the code

Action

As mentioned above, actions are essentially ordinary objects that contain the type attribute. This type is key to our implementation of user behavior tracking. For example, adding a to-do action might look something like this:

{
  type: 'ADD_TODO'.payload: {
    id: 1.content: 'To-do List 1'.completed: false}}Copy the code

If you need to add a charge item, you’re essentially writing the payload in the code above to the state.todos array. Keep it in suspense.)

{
  counter: 0.todos: [{
    id: 1.content: 'To-do List 1'.completed: false}}]Copy the code
Action Creator

Action Creator can be synchronous or asynchronous

As the name suggests, Action Creator is the Creator of Action, which is essentially a function that returns an Action (object). For example, here is an Action Creator for “Add a to-do” :

var id = 1
function addTodo(content) {
  return {
    type: 'ADD_TODO'.payload: {
      id: id++,
      content: content, // To-do list
      completed: false  // Whether to complete the flag}}}Copy the code

In layman’s terms, Action Creator is used for actions bound to the user (button clicks, etc.) and its return value Action is used for subsequent dispatches (actions)

Redux knows that action.payload is written to the state.todos array after store.dispatch. And who is responsible for “writing”? The suspense is about to be revealed…

Reducer

Reducer must be a synchronous pure function

After each dispatch(action), the reducer will be triggered. The essence of reducer execution is a function. Update state according to action.type and return nextState. Finally, the original state will be completely replaced by the return value nextState from the reducer

Note: The above “update” does not mean that reducer can directly modify the state specified by Redux. A copy of state must be made first and the modification operation should be done on the copy nextState. For example, cloneDeep of Lodash can be used. You can also use object. assign/map/filter/… A function that returns a copy, etc

The reducer of the to-do list mentioned in Action Creator above looks like this (ES6 / immutable.js is not used here for ease of understanding) :

var initState = {
  counter: 0.todos: []}function reducer(state, action) {
  // โ€ป The initial state of the application was set at the first reducer. โ€ป
  if(! state) state = initStateswitch (action.type) {
    case 'ADD_TODO':
      var nextState = _.cloneDeep(state) // The lodash clone is used
      nextState.todos.push(action.payload) 
      return nextState

    default:
    // Because nextState replaces the entire state
    // If there is no modification, the original state must be returned.
      return state
  }
}
Copy the code

ยง summary

  • storeFrom the storycreateStore(reducer)generate
  • statethroughstore.getState()Fetch, in essence, is generally a store of the entire application stateobject
  • actionIt’s essentially a containtypeCommonality of attributesobject, created by Action Creator (function)
  • changestateMust bedispatchaaction
  • reducerIt’s essentially based onaction.typeTo update thestateAnd returnnextState ็š„function
  • reducerMust return a value, otherwisenextStateIs theundefined
  • In fact,stateIs that allreducerA summary of returned valuesThere is only one tutorialreducer, mainly because the application scenarios are relatively simple)

Action Creator => Action => Store. dispatch(Action) => Reducer (state, Action) => Original state State = nextState

Redux vs. traditional backend MVC

Redux Traditional back-end MVC
store Database instance
state Data stored in a database
dispatch(action) User initiates a request
action: { type, payload } typeRepresents the requested URL,payloadRepresents requested data
reducer Route + Controller (Handler)
reducerIn theswitch-casebranch Route, according toaction.typeRoute to the corresponding controller
reducerWithin thestateThe processing of The controller adds, deletes, or modifies the database
reducerreturnnextState Write the modified record back to the database

Redux principle

start

Before I begin, I want to talk about a common design pattern: the observer pattern. First, my personal interpretation of the observer pattern: the Publish/Subscribe pattern. For those of you who are familiar with this pattern you can skip this code. If you are not sure, you can try the following code!

Observer model

The observer pattern defines a one-to-many dependency that allows multiple observer objects to listen to a target object at the same time. When the state of the target object changes, all observer objects are notified so that they can update automatically. – Graphic Design Patterns

The observer pattern, is most frequently used in all JavaScript design patterns, the interview is the highest frequency of design patterns, so it is very important – if I were the interviewer, considering the interview time is limited, design patterns, this can’t ask more, I may when your design pattern will just ask the observer pattern that a pattern. The model is heavily weighted.

The key is not necessarily the difficult. The observer model is important, but it is not abstract and not difficult to understand. This pattern is common not only in business development, but also in everyday life. To help you get an idea, before we get into the code world, let’s take a look at a routine:

The observer model in life

Monday just went to work, front-end development pu was product manager Xiao Hong pulled into a nail group – “staff management system requirements 99th change group”. In this group, not only Pup, but also back-end developer A, testing student B. Three technical students saw this simple straightforward group name immediately ready to accept the change, ready to roll up their sleeves and start to work. At this time, Xiao Hong said: “Don’t worry. There is something wrong with this demand. I need to confirm with the business side again. In this case, the three technical students do not have to work immediately, but they are ready to make a new requirement this week, waiting for the call of the product manager.

One day passed, two days passed. On Wednesday afternoon, Xiao Hong finally confirmed all the details of the requirements with the business side, so she shouted in the “99th change Group of employee management System requirements” : “Here comes the requirement document!” “, then threw out the “requirements document.zip” file and @ everyone. When the three technical students heard the familiar prompt sound of “Someone @ me”, they immediately opened the group to check the group news and group files, and then invested in their own development according to the demand information provided by the group news and group files. This process is a typical observer pattern.

Key roles are chosen accordingly

The Observer pattern has an “alias” called the publish-subscribe pattern (the alias is in quotes because there are subtle differences between the two, which I’ll cover below). This nickname vividly illustrates the two core role elements of the Observer pattern: “publisher” and “subscriber.” In the above process, there is only one publisher of the requirements document (target object) – product manager Xiao Hong. And demand information of the receiver has multiple — the front-end and back-end, testing of students, these students in common is that they need according to demand information to carry out the follow-up work, so are very concerned about the demand information, and had to focus on the alerted to this group of the group, they are solid subscriber, object or observer.

Now let’s go back to the slightly abstract definition we mentioned at the beginning:

The observer pattern defines a one-to-many dependency that allows multiple observer objects to listen to a target object at the same time. When the state of the target object changes, all observer objects are notified so that they can update automatically.

In the spike group above, one requirement information object corresponds to multiple observers (technical students). When the state of the requirement information object changes (from nothing to nothing), the product manager informs all students in the group so that they can receive the information and carry out work: Role division -> state changes -> publisher notifies subscribers, this is the “trope” of observer mode.

Understand the definition in practice

Combined with our analysis above, it is now known that at least two key roles must be present in the observer model — publisher and subscriber. In object-oriented terms, there are two classes.

Let’s start with the class that represents the Publisher, which we’ll call Publisher. What “basic skills” should this class have? Remember han Meimei above, what is the basic operation of Han Meimei? First, pull (adding subscribers) and then @everyone (notifying subscribers) are the most obvious. In addition, as group leader & product manager, Han meimei also has the ability to kick out project team members (remove subscribers). OK, now that the three basic capabilities of the product Manager publisher class are complete, let’s start writing code:

// Define the publisher class
class Publisher {
  constructor() {
    this.observers = []
    console.log('Publisher created')}// Add subscribers
  add(observer) {
    console.log('Publisher.add invoked')
    this.observers.push(observer)
  }
  // Remove the subscriber
  remove(observer) {
    console.log('Publisher.remove invoked')
    this.observers.forEach((item, i) = > {
      if (item === observer) {
        this.observers.splice(i, 1)}}}// Notify all subscribers
  notify() {
    console.log('Publisher.notify invoked')
    this.observers.forEach((observer) = > {
      observer.update(this)}}}Copy the code

Ok, now that the Publisher is done, let’s think about what the subscriber can do — the subscriber’s power is very simple, as a passive party, it has only two actions — to be notified and to perform (essentially to receive calls from the Publisher, which we’ve already done in Publisher). Since we are doing method calls in Publisher, all we need to do in the subscriber class is define the method:

// Define the subscriber class
class Observer {
    constructor() {
        console.log('Observer created')}update() {
        console.log('Observer.update invoked')}}Copy the code

That completes the design and writing of the most basic publisher and subscriber classes. In real business development, all of our custom publisher/subscriber logic can be rewritten based on these two basic classes. For example, we can extend the publisher class to allow all subscribers to listen for changes in a particular state. Using the previous example, we asked developers to listen for changes to the requirements document (PRD) :

// Define a concrete requirements document (PRD) publication class
class PrdPublisher extends Publisher {
    constructor() {
        super(a)// Initialize the requirements document
        this.prdState = null
        // Xiao Hong has not pulled the group yet, the development group is currently empty
        this.observers = []
        console.log('PrdPublisher created')}// This method is used to get the current prdState
    getState() {
        console.log('PrdPublisher.getState invoked')
        return this.prdState
    }
    
    // This method is used to change the prdState value
    setState(state) {
        console.log('PrdPublisher.setState invoked')
        // The value of PRD has changed
        this.prdState = state
        // Notify all developers immediately of changes to requirements documentation
        this.notify()
    }
}
Copy the code

As subscribers, the developer’s task becomes concrete: receive the requirements document and get to work:

class DeveloperObserver extends Observer {
    constructor() {
        super(a)// The requirements document does not yet exist, and the PRD is initially an empty object
        this.prdState = {}
        console.log('DeveloperObserver created')}// Override a specific update method
    update(publisher) {
        console.log('DeveloperObserver.update invoked')
        // Update the requirements document
        this.prdState = publisher.getState()
        // Call the worker function
        this.work()
    }
    
    // Work method, a specialized method of moving bricks
    work() {
        // Get the requirements document
        const prd = this.prdState
        // Start moving bricks based on the information provided in the requirements document....console.log('996 begins... ')}}Copy the code

Next, we can create a new PrdPublisher object (the product manager) that can update the requirements document by calling the setState method. Each update to the requirements document is followed by a call to notify all developers. This implements what the definition calls:

When the state of the target object changes, all observer objects are notified so that they can be updated automatically.

OK, let’s take a look at how Xiao Hong and her friends make things work:

// Create subscribers: front-end development poof
const liLei = new DeveloperObserver()
// Create subscriber: server developer A (sorry... It's really hard to name)
const A = new DeveloperObserver()
// Create subscriber: test student B
const B = new DeveloperObserver()
// Xiao Hong appears
const hanMeiMei = new PrdPublisher()
// The requirements document appears
const prd = {
    // Specific requirements. }// Xiao Hong starts to pull the group
hanMeiMei.add(liLei)
hanMeiMei.add(A)
hanMeiMei.add(B)
// Xiao Hong sends the requirements document and @everyone
hanMeiMei.setState(prd)
Copy the code

That is the complete implementation flow of the Observer pattern in the code world.

I believe that by this step, you have a good grasp of the core idea of the observer mode, the basic implementation mode.

What is the difference between the observer model and the publish-subscribe model?

During the interview process, some detail-oriented interviewers may ask the difference between the observer model and the publish-subscribe model. This question may cause some discomfort, as numerous references and printed books tell you that “publish-subscribe and observer are two names for the same thing.” In the previous description of this book, the distinction between the two is not highlighted. In fact, these two models, to be more true, really can not be given a strict equal sign.

Why does everyone like to forcibly equalize them? This is because even if the equal sign is drawn, it does not affect our normal use. After all, there is no essential difference between the two in core ideas and operation mechanism. But considering that this question can really be a direction of interview questions, we’ll take it out separately here.

Back to our previous example. Xiao Hong put all the developers in a group and threw the requirements document directly to each member of the group. This operation, where publishers reach subscribers directly, is called observer mode. But if there is no pull a group of little red, but the requirements document to upload to the company the demand of unified platform, demand platform perception changes to files and automatically notify each subscribed to the developers of the document, the publisher does not directly touch the subscriber, but by unity of the operation of the third party to do the actual communication, is called the publish-subscribe pattern.

As you can already see, the difference between the observer model and the public-subscribe model is the presence of a third party and the ability of the publisher to directly perceive the subscriber (see figure).

In the examples we’ve seen, the operation of the little red pull spike group is typical of the observer pattern; Implementing event listening/publishing via EventBus is a publish-subscribe model.

If you have yu, how can you have liang? Why do you need a publish-subscribe model when you have an observer model?

Think about it: Why is there an observer model? The observer mode, in fact, solves the coupling problem between modules. With it, even two separate and unrelated modules can realize data communication. However, the observer mode only reduces the coupling, but does not completely solve the coupling problem — the observed must maintain a set of observers, who must implement a unified method for the called by the observed, and there is still an ambiguous relationship between the two.

Publish-subscribe, on the other hand, is a quick fix — the publisher is not aware of the subscriber, does not care how it implements callback methods, and events are registered and triggered on a third-party platform (the event bus) that is independent of both parties. Complete decoupling is achieved in publish-subscribe mode.

But that doesn’t mean the publish-subscribe model is “superior” to the observer model. In practical development, our module decoupling appeal does not always require complete decoupling. If the relationship between the two modules is stable and necessary, then the observer pattern is sufficient. We tend to use publish-subscribe when there is a lot of independence between modules and there is no need to create dependencies between the two purely for data communication.

The observer pattern, based on a topic/event channel, allows the object that wants to receive notifications (called subscriber) to be notified by subscribing to a topic through a custom event and by delivering the topic event. Just as users subscribe to wechat public account, as long as the release, users can receive the latest content.

/** * describe: Implement an observer pattern */
let data = {
    hero: 'the phoenix'};// An array to store subscribers
let subscribers = [];
// Subscribe to add subscriber method
const addSubscriber = function(fn) {
    subscribers.push(fn)
}
/ / release
const deliver = function(name) {
    data.hero = name;
    // Call (notify) all methods when data changes (subscriber)
    for(let i = 0; i<subscribers.length; i++){
        const fn = subscribers[i]
        fn()
    }
}
// Initiate a subscription by addSubscriber
addSubscriber(() = > {
    console.log(data.hero)
})
// Change data, the name is automatically printed
deliver('spring') 
deliver('the fox')
deliver('the CARDS')
Copy the code

The publish subscription stores the subscriber (method fn) through addSubscriber, which is automatically iterated to execute the FN method inside when data is changed through a call to Deliver.

Relearn Redux

First let's optimize the publish subscribe code above, and change the name by the way. Why do we change the name? Mainly following in the footsteps of Redux. Let the students more familiar.let state = {hero: 'the phoenix'};
let subscribers = [];
// Subscribe defines a subscribe
const subscribe = (fn) = > {
    subscribers.push(fn)
}
/ / release
const dispatch = (name) = > {
    state.hero = name;
    // Call (notify) all methods when data changes (subscriber)
    subscribers.forEach(fn= >fn())
}
// Initiate a subscription with subscribe
subscribe(() = > {
    console.log(state.hero)
})
// Change the state to print the name automatically
// The state cannot be changed directly
dispatch('spring') 
dispatch('the fox')
dispatch('the CARDS')
Copy the code

Now does this look familiar? Yes, it’s an idea similar to Redux’s change of state. But a publish subscription alone is not enough, it is not possible to change a state that requires so many methods to be defined. So we sealed him up.

CreatStore method
const creatStore = (initState) = > {
    let state = initState;
    let subscribers = [];
    // Subscribe defines a subscribe
    const subscribe = (fn) = > {
        subscribers.push(fn)
    }
    / / release
    const dispatch = (currentState) = > {
        state = currentState;
        // Call (notify) all methods when data changes (subscriber)
        subscribers.forEach(fn= >fn())
    }
    // We need to add this method to get state
    const getState = () = > {
        return state;
    }
    return {
        subscribe,
        dispatch,
        getState,
    }
}
Copy the code

This creates a createStore method. There’s nothing new, just pass in an initial state, and then return subscribe, Dispatch, and getState. There’s a new getState method, and the code is very simple just a return state in order to get the state.

CreatStore use

Now that we’ve implemented createStore, let’s see how we can use it. What about the classic example of a counter

let initState = {
    num: 0,}const store = creatStore(initState);
/ / subscribe
store.subscribe(() = > {
    let state = store.getState()
    console.log(state.num)
})
/ / + 1
store.dispatch({
   num: store.getState().num + 1
})
/ / 1
store.dispatch({
   num: store.getState().num - 1}) copy the codeCopy the code

This looks a little bit like Redux. But there’s a problem with that. If you use the store.dispatch method, if you write something wrong in the middle or pass something else, it will be more troublesome. Like this:

In fact, I want +1, +1, -1 should be 1 (initial num is 0)! But if YOU write one wrong you’re going to get the rest wrong. And he also has the problem of being able to give a new state at will. Then it doesn’t seem so innocent. Like this:

Because num is no longer Number, NaN will be printed, which is not what we want. So we’re going to revamp dispatch to make it simpler. So how do you do that? We invited a manager to help us manage, and we named him reducer for the time being

Why reducer

Check out the Chinese Redux website. Here’s what he says:

This function is called reducer because it is the same as array.prototype. reduce(reducer,? The callback functions in initialValue are of the same type. It is important to keep reducer pure. Never do these operations on a Reducer.

Ah, the translation seems to be a lot clearer. Inspired by the reduce method in arrays, it is a synthesis of operations, as noted by the commenter below. With that said, LET me introduce Reduce.

What is the reduce

Without further ado, get right to the code

const array1 = [1.2.3.4];
const reducer = (accumulator, currentValue) = > accumulator + currentValue;

// 1 + 2 + 3 + 4
console.log(array1.reduce(reducer));
// expected output: 10

// 5 + 1 + 2 + 3 + 4
console.log(array1.reduce(reducer, 5));
// expected output: 15

/* This deceleration takes four parameters: * Accumulator (ACC) * Current value (CUR) * Current index (IDX) * source array (SRC) * The return value from your Reducer function is assigned to the accumulator and its value is remembered in each iteration of the entire array and eventually becomes the final single result value. * /
Copy the code

Specific Parameters

The callback function is executed on each element in the array and takes four arguments: the return value of the accumulator callback; It is the cumulative value previously returned in a callback call, or initialValue, if provided (see below). CurrentValue The current element is processed in an array. CurrentIndex Index of the current element being processed in the optional array. If initialValue provides an, from the index0Start from index otherwise1Start array Optional the array reduce() is summoned. InitialValue The optional value callback used as the first argument of the first call. If no initial value is provided, the first element in the array is used. It is an error to call reduce() with an empty array without an initial value. Copy the codeCopy the code

This method is relatively more difficult to understand than forEach, map, filter. See array.prototype.reduce () for details

Note: First of all, I would like to thank the following commentator PandA080 for his guidance. With his advice, I went back to the official website of Rudex. Through learning, I have a better understanding of reducer and reduce Reducer official website

Ps: After understanding, I actually felt that reducer was a very strange name from the perspective of translation. Maybe English is limited, maybe he has a more appropriate meaning I don't know yet.Copy the code
What is the reducer

Reducer in my learning process, I thought that reducer was a manager (maybe this was incorrect). Then we informed the reducer every time we wanted to do something and asked him to do it according to what we said. If we accidentally say the wrong thing, then he won’t do it. Go straight to the default. Reducer on!!

function reducer(state, action) {
    // Let the manager match what to do with the action. Type passed in
    switch (action.type){
        case 'add':
            return {
                ...state,
                count: state.count + 1
            }
        case 'minus':
            return {
                ...state,
                count: state.count - 1
            }
        // Return the default value if no method is matched
        default:
            returnstate; }}Copy the code

With this manager added, we need to rewrite the previous createStroe method: Put the Reducer in

const creatStore = (reducer,initState) = > {
    let state = initState;
    let subscribers = [];
    // Subscribe defines a subscribe
    const subscribe = (fn) = > {
        subscribers.push(fn)
    }
    / / release
    const dispatch = (action) = > {
        state = reducer(state,action);
        subscribers.forEach(fn= >fn())
    }
    const getState = () = > {
        return state;
    }
    return {
        subscribe,
        dispatch,
        getState,
    }
}
Copy the code

It is a very simple modification. In order to make it easy for you to see the modification and the difference, I specially re-code the comparison between the two methods before and after, as shown in the picture below

Ok, now let’s try creatStore with the administrator added.

function reducer(state, action) {
    // Let the manager match what to do with the action. Type passed in
    switch (action.type){
        case 'add':
            return {
                ...state,
                num: state.num + 1
            }
        case 'minus':
            return {
                ...state,
                num: state.num - 1
            }
        // Return the default value if no method is matched
        default:
            returnstate; }}let initState = {
    num: 0,}const store = creatStore(reducer,initState);
/ / subscribe
store.subscribe(() = > {
    let state = store.getState()
    console.log(state.num)
})
Copy the code

To see the results, I output dispatch directly to the console, as shown below:And it works out pretty well, because we don’t have NaN or any other nondescribable problems because of writing errors. Now the dispatch is a little purer.

We just give it a Type and let the manager handle how to change the state for us. If we accidentally make a typo, or give a type, then the manager can’t match the action, then our dispatch will be invalid, and we will return our own default state.

Well, this is basically what I saw in my mind when I first used Redux. It was very difficult for me to use it at that time. After barely implementing the counter demo, I quietly shut down VS Code.

Next we will improve the Reducer and add another method to it. And let’s give state another one this time

function reducer(state, action) {
    // Let the manager match what to do with the action. Type passed in
    switch (action.type){
        case 'add':
            return {
                ...state,
                num: state.num + 1
            }
        case 'minus':
            return {
                ...state,
                num: state.num - 1
            }
        // Add a method that can pass parameters to make it more flexible
        case 'changeNum':
            return {
                ...state,
                num: state.num + action.val
            }
        // Return the default value if no method is matched
        default:
            returnstate; }}let initState = {
    num: 0,}const store = creatStore(reducer,initState);
/ / subscribe
store.subscribe(() = > {
    let state = store.getState()
    console.log(state.num)
})
Copy the code

The console uses the new method again:

Well, doesn’t that make Dispatch more flexible?

Now we have written three methods in the Reducer, but in the actual project, there must be many methods, so if we keep writing like this, it will not be conducive to development and maintenance. So I’ll leave that to you to think about.

conclusion

In the second part of this column, we learned the basics of Redux, and there will be more exciting ones to come

โค๏ธ thank you

If you think this content is quite helpful to you: click the like to support it, so that more people can see this content (collection does not like, are playing hoagie – -) pay attention to the public account to NPY front-end secrets, we learn together and progress together. If you feel good, you can also read other articles (thanks to friends for their encouragement and support ๐ŸŒน๐ŸŒน๐ŸŒน)

Start the LeetCode journey

Double pointer to LeetCode

Leet27. Remove elements

Front-end engineers must learn the classic sorting algorithm

LeetCode20. Brackets match

LeetCode7, integer inversion

Copyright belongs to the author. Commercial reprint please contact the author for authorization, non-commercial reprint please indicate the source.