One place for hosting & domains

      How To Build a REST API with Prisma and PostgreSQL


      The author selected the Diversity in Tech Fund to receive a donation as part of the Write for DOnations program.

      Introduction

      Prisma is an open source database toolkit. It consists of three main tools:

      • Prisma Client: An auto-generated and type-safe query builder for Node.js and TypeScript.
      • Prisma Migrate: A declarative data modeling and migration system.
      • Prisma Studio: A GUI to view and edit data in your database.

      These tools aim to increase an application developer’s productivity in their database workflows. One of the top benefits of Prisma is the level of abstraction it provides: Instead of figuring out complex SQL queries or schema migrations, application developers can reason about their data in a more intuitive way when using Prisma to work with their database.

      In this tutorial, you will build a REST API for a small blogging application in TypeScript using Prisma and a PostgreSQL database. You will set up your PostgreSQL database locally with Docker and implement the REST API routes using Express. At the end of the tutorial, you will have a web server running locally on your machine that can respond to various HTTP requests and read and write data in the database.

      Prerequisites

      This tutorial assumes the following:

      Basic familiarity with TypeScript and REST APIs is helpful but not required for this tutorial.

      Step 1 — Creating Your TypeScript Project

      In this step, you will set up a plain TypeScript project using npm. This project will be the foundation for the REST API you’re going to build throughout the course of this tutorial.

      First, create a new directory for your project:

      Next, navigate into the directory and initialize an empty npm project. Note that the -y option here means that you’re skipping the interactive prompts of the command. To run through the prompts, remove -y from the command:

      For more details on these prompts, you can follow Step 1 in How To Use Node.js Modules with npm and package.json.

      You’ll receive output similar to the following with the default responses in place:

      Output

      Wrote to /.../my-blog/package.json: { "name": "my-blog", "version": "1.0.0", "description": "", "main": "index.js", "scripts": { "test": "echo "Error: no test specified" && exit 1" }, "keywords": [], "author": "", "license": "ISC" }

      This command creates a minimal package.json file that you use as the configuration file for your npm project. You’re now ready to configure TypeScript in your project.

      Execute the following command for a plain TypeScript setup:

      • npm install typescript ts-node @types/node --save-dev

      This installs three packages as development dependencies in your project:

      • typescript: The TypeScript toolchain.
      • ts-node: A package to run TypeScript applications without prior compilation to JavaScript.
      • @types/node: The TypeScript type definitions for Node.js.

      The last thing to do is to add a tsconfig.json file to ensure TypeScript is properly configured for the application you’re going to build.

      First, run the following command to create the file:

      Add the following JSON code into the file:

      my-blog/tsconfig.json

      {
        "compilerOptions": {
          "sourceMap": true,
          "outDir": "dist",
          "strict": true,
          "lib": ["esnext"],
          "esModuleInterop": true
        }
      }
      

      Save and exit the file.

      This is a standard and minimal configuration for a TypeScript project. If you want to learn about the individual properties of the configuration file, you can look them up in the TypeScript documentation.

      You’ve set up your plain TypeScript project using npm. Next you’ll set up your PostgreSQL database with Docker and connect Prisma to it.

      Step 2 — Setting Up Prisma with PostgreSQL

      In this step, you will install the Prisma CLI, create your initial Prisma schema file, and set up PostgreSQL with Docker and connect Prisma to it. The Prisma schema is the main configuration file for your Prisma setup and contains your database schema.

      Start by installing the Prisma CLI with the following command:

      • npm install @prisma/cli --save-dev

      As a best practice, it is recommended to install the Prisma CLI locally in your project (as opposed to a global installation). This helps avoid version conflicts in case you have more than one Prisma project on your machine.

      Next, you’ll set up your PostgreSQL database using Docker. Create a new Docker Compose file with the following command:

      Now add the following code to the newly created file:

      my-blog/docker-compose.yml

      version: '3.8'
      services:
        postgres:
          image: postgres:10.3
          restart: always
          environment:
            - POSTGRES_USER=sammy
            - POSTGRES_PASSWORD=your_password
          volumes:
            - postgres:/var/lib/postgresql/data
          ports:
            - '5432:5432'
      volumes:
        postgres:
      

      This Docker Compose file configures a PostgreSQL database that can be accessed via port 5432 of the Docker container. Also note that the database credentials are currently set as sammy (user) and your_password (password). Feel free to adjust these credentials to your preferred user and password. Save and exit the file.

      With this setup in place, go ahead and launch the PostgreSQL database server with the following command:

      The output of this command will be similar to this:

      Output

      Pulling postgres (postgres:10.3)... 10.3: Pulling from library/postgres f2aa67a397c4: Pull complete 6de83ca23e55: Pull complete . . . Status: Downloaded newer image for postgres:10.3 Creating my-blog_postgres_1 ... done

      You can verify that the database server is running with the following command:

      This will output something similar to this:

      Output

      CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES 8547f8e007ba postgres:10.3 "docker-entrypoint.s…" 3 seconds ago Up 2 seconds 0.0.0.0:5432->5432/tcp my-blog_postgres_1

      With the database server running, you can now create your Prisma setup. Run the following command from the Prisma CLI:

      This will print the following output:

      Output

      ✔ Your Prisma schema was created at prisma/schema.prisma. You can now open it in your favorite editor.

      Note that as a best practice, you should prefix all invocations of the Prisma CLI with npx. This ensures your local installation is being used.

      After you ran the command, the Prisma CLI created a new folder called prisma in your project. It contains the following two files:

      • schema.prisma: The main configuration file for your Prisma project (will include your data model).
      • .env: A dotenv file to define your database connection URL.

      To make sure Prisma knows about the location of your database, open the .env file and adjust the DATABASE_URL environment variable.

      First open the .env file:

      Now you can set the environment variable as follows:

      my-blog/prisma/.env

      DATABASE_URL="postgresql://sammy:your_password@localhost:5432/my-blog?schema=public"
      

      Make sure to change the database credentials to the ones you specified in the Docker Compose file. To learn more about the format of the connection URL, visit the Prisma docs.

      Once you’re done, save and exit the file.

      In this step, you set up your PostgreSQL database with Docker, installed the Prisma CLI, and connected Prisma to the database via an environment variable. In the next section, you’ll define your data model and create your database tables.

      Step 3 — Defining Your Data Model and Creating Database Tables

      In this step, you will define your data model in the Prisma schema file. This data model will then be mapped to the database with Prisma Migrate, which will generate and send the SQL statements for creating the tables that correspond to your data model. Since you’re building a blogging application, the main entities of the application will be users and posts.

      Prisma uses its own data modeling language to define the shape of your application data.

      First, open your schema.prisma file with the following command:

      • nano prisma/schema.prisma

      Now, add the following model definitions to it. You can place the models at the bottom of the file, right after the generator client block:

      my-blog/prisma/schema.prisma

      . . .
      model User {
        id    Int     @default(autoincrement()) @id
        email String  @unique
        name  String?
        posts Post[]
      }
      
      model Post {
        id        Int     @default(autoincrement()) @id
        title     String
        content   String?
        published Boolean @default(false)
        author    User?   @relation(fields: [authorId], references: tag:www.digitalocean.com,2005:/community/tutorials/how-to-build-a-rest-api-with-prisma-and-postgresql)
        authorId  Int?
      }
      

      Save and exit the file.

      You are defining two models, called User and Post. Each of these has a number of fields that represent the properties of the model. The models will be mapped to database tables; the fields represent the individual columns.

      Also note that there’s a one-to-many relation between the two models, specified by the posts and author relation fields on User and Post. This means that one user can be associated with many posts.

      With these models in place, you can now create the corresponding tables in the database using Prisma Migrate. In your terminal run the following command:

      • npx prisma migrate save --experimental --create-db --name "init"

      This command creates a new migration on your filesystem. Here’s a quick overview of the three options that are provided to the command:

      • --experimental: Required because Prisma Migrate is currently in an experimental state.
      • --create-db: Enables Prisma Migrate to create the database named my-blog that’s specified in the connection URL.
      • --name "init": Specifies the name of the migration (will be used to name the migration folder that’s created on your filesystem).

      The output of this command will be similar to this:

      Output

      New datamodel: // This is your Prisma schema file, // learn more about it in the docs: https://pris.ly/d/prisma-schema datasource db { provider = "postgresql" url = env("DATABASE_URL") } generator client { provider = "prisma-client-js" } model User { id Int @default(autoincrement()) @id email String @unique name String? posts Post[] } model Post { id Int @default(autoincrement()) @id title String content String? published Boolean @default(false) author User? @relation(fields: [authorId], references: tag:www.digitalocean.com,2005:/community/tutorials/how-to-build-a-rest-api-with-prisma-and-postgresql) authorId Int? } Prisma Migrate just created your migration 20200811140708-init in migrations/ └─ 20200811140708-init/ └─ steps.json └─ schema.prisma └─ README.md

      Feel free to explore the migration files that have been created in the prisma/migrations directory.

      To run the migration against your database and create the tables for your Prisma models, run the following command in your terminal:

      • npx prisma migrate up --experimental

      You’ll receive the following output:

      Output

      . . . Checking the datasource for potential data loss... Database Changes: Migration Database actions Status 20200811140708-init 2 CreateTable statements. Done 🚀 You can get the detailed db changes with prisma migrate up --experimental --verbose Or read about them here: ./migrations/20200811140708-init/README.md 🚀 Done with 1 migration in 206ms.

      Prisma Migrate now generates the SQL statements that are required for the migration and sends them to the database. The following are the SQL statements that created the tables:

      CREATE TABLE "public"."User" (
        "id" SERIAL,
        "email" text  NOT NULL ,
        "name" text   ,
        PRIMARY KEY ("id")
      )
      
      CREATE TABLE "public"."Post" (
        "id" SERIAL,
        "title" text  NOT NULL ,
        "content" text   ,
        "published" boolean  NOT NULL DEFAULT false,
        "authorId" integer   ,
        PRIMARY KEY ("id")
      )
      
      CREATE UNIQUE INDEX "User.email" ON "public"."User"("email")
      
      ALTER TABLE "public"."Post" ADD FOREIGN KEY ("authorId")REFERENCES "public"."User"("id") ON DELETE SET NULL ON UPDATE CASCADE
      

      In this step, you defined your data model in your Prisma schema and created the respective databases tables with Prisma Migrate. In the next step, you’ll install Prisma Client in your project so that you can query the database.

      Step 4 — Exploring Prisma Client Queries in a Plain Script

      Prisma Client is an auto-generated and type-safe query builder that you can use to programmatically read and write data in a database from a Node.js or TypeScript application. You will use it for database access within your REST API routes, replacing traditional ORMs, plain SQL queries, custom data access layers, or any other method of talking to a database.

      In this step, you will install Prisma Client and get familiar with the queries you can send with it. Before implementing the routes for your REST API in the next steps, you will first explore some of the Prisma Client queries in a plain, executable script.

      First, go ahead and install Prisma Client in your project by opening up your terminal and installing the Prisma Client npm package:

      • npm install @prisma/client

      Next, create a new directory called src that will contain your source files:

      Now create a TypeScript file inside of the new directory:

      All of the Prisma Client queries return promises that you can await in your code. This requires you to send the queries inside of an async function.

      Add the following boilerplate with an async function that’s executed in your script:

      my-blog/src/index.ts

      import { PrismaClient } from '@prisma/client'
      
      const prisma = new PrismaClient()
      
      async function main() {
        // ... your Prisma Client queries will go here
      }
      
      main()
        .catch((e) => console.error(e))
        .finally(async () => await prisma.disconnect())
      

      Here’s a quick breakdown of the boilerplate:

      1. You import the PrismaClient constructor from the previously installed @prisma/client npm package.
      2. You instantiate PrismaClient by calling the constructor and obtain an instance called prisma.
      3. You define an async function called main where you’ll add your Prisma Client queries next.
      4. You call the main function, while catching any potential exceptions and ensuring Prisma Client closes any open database connections by calling prisma.disconnect().

      With the main function in place, you can start adding Prisma Client queries to the script. Adjust index.ts to look as follows:

      my-blog/src/index.ts

      import { PrismaClient } from '@prisma/client'
      
      const prisma = new PrismaClient()
      
      async function main() {
        const newUser = await prisma.user.create({
          data: {
            name: 'Alice',
            email: '[email protected]',
            posts: {
              create: {
                title: 'Hello World',
              },
            },
          },
        })
        console.log('Created new user: ', newUser)
      
        const allUsers = await prisma.user.findMany({
          include: { posts: true },
        })
        console.log('All users: ')
        console.dir(allUsers, { depth: null })
      }
      
      main()
        .catch((e) => console.error(e))
        .finally(async () => await prisma.disconnect())
      

      In this code, you’re using two Prisma Client queries:

      • create: Creates a new User record. Notice that you’re actually using a nested write, meaning you’re creating both a User and Post record in the same query.
      • findMany: Reads all existing User records from the database. You’re providing the include option that additionally loads the related Post records for each User record.

      Now run the script with the following command:

      You will receive the following output in your terminal:

      Output

      Created new user: { id: 1, email: '[email protected]', name: 'Alice' } [ { id: 1, email: '[email protected]', name: 'Alice', posts: [ { id: 1, title: 'Hello World', content: null, published: false, authorId: 1 } ] }

      Note: If you are using a database GUI you can validate that the data was created by looking at the User and Post tables. Alternatively, you can explore the data in Prisma Studio by running npx prisma studio --experimental.

      You’ve now used Prisma Client to read and write data in your database. In the remaining steps, you’ll apply that new knowledge to implement the routes for a sample REST API.

      Step 5 — Implementing Your First REST API Route

      In this step, you will install Express in your application. Express is a popular web framework for Node.js that you will use to implement your REST API routes in this project. The first route you will implement will allow you to fetch all users from the API using a GET request. The user data will be retrieved from the database using Prisma Client.

      Go ahead and install Express with the following command:

      Since you’re using TypeScript, you’ll also want to install the respective types as development dependencies. Run the following command to do so:

      • npm install @types/express --save-dev

      With the dependencies in place, you can set up your Express application.

      Start by opening your main source file again:

      Now delete all the code in index.ts and replace it with the following to start your REST API:

      my-blog/src/index.ts

      import { PrismaClient } from '@prisma/client'
      import express from 'express'
      
      const prisma = new PrismaClient()
      const app = express()
      
      app.use(express.json())
      
      // ... your REST API routes will go here
      
      app.listen(3000, () =>
        console.log('REST API server ready at: http://localhost:3000'),
      )
      

      Here’s a quick breakdown of the code:

      1. You import PrismaClient and express from the respective npm packages.
      2. You instantiate PrismaClient by calling the constructor and obtain an instance called prisma.
      3. You create your Express app by calling express().
      4. You add the express.json() middleware to ensure JSON data can be processed properly by Express.
      5. You start the server on port 3000.

      Now you can implement your first route. Between the calls to app.use and app.listen, add the following code:

      my-blog/src/index.ts

      . . .
      app.use(express.json())
      
      app.get('/users', async (req, res) => {
        const users = await prisma.user.findMany()
        res.json(users)
      })
      
      app.listen(3000, () =>
      console.log('REST API server ready at: http://localhost:3000'),
      )
      

      Once added, save and exit your file. Then start your local web server using the following command:

      You will receive the following output:

      Output

      REST API server ready at: http://localhost:3000

      To access the /users route you can point your browser to http://localhost:3000/users or any other HTTP client.

      In this tutorial, you will test all REST API routes using curl, a terminal-based HTTP client.

      Note: If you prefer to use a GUI-based HTTP client, you can use alternatives like Postwoman or the Advanced REST Client.

      To test your route, open up a new terminal window or tab (so that your local web server can keep running) and execute the following command:

      • curl http://localhost:3000/users

      You will receive the User data that you created in the previous step:

      Output

      [{"id":1,"email":"[email protected]","name":"Alice"}]

      Note that the posts array is not included this time. This is because you’re not passing the include option to the findMany call in the implementation of the /users route.

      You’ve implemented your first REST API route at /users. In the next step you will implement the remaining REST API routes to add more functionality to your API.

      Step 6 — Implementing the Remaining REST API Routes

      In this step, you will implement the remaining REST API routes for your blogging application. At the end, your web server will serve various GET, POST, PUT, and DELETE requests.

      Here is an overview of the different routes you will implement:

      HTTP MethodRouteDescription
      GET/feedFetches all published posts.
      GET/post/:idFetches a specific post by its ID.
      POST/userCreates a new user.
      POST/postCreates a new post (as a draft).
      PUT/post/publish/:idSets the published field of a post to true.
      DELETEpost/:idDeletes a post by its ID.

      Go ahead and implement the remaining GET routes first.

      Open up the index.ts with the following command:

      Next, add the following code following the implementation of the /users route:

      my-blog/src/index.ts

      . . .
      
      app.get('/feed', async (req, res) => {
        const posts = await prisma.post.findMany({
          where: { published: true },
          include: { author: true }
        })
        res.json(posts)
      })
      
      app.get(`/post/:id`, async (req, res) => {
        const { id } = req.params
        const post = await prisma.post.findOne({
          where: { id: Number(id) },
        })
        res.json(post)
      })
      
      app.listen(3000, () =>
        console.log('REST API server ready at: http://localhost:3000'),
      )
      

      Save and exit your file.

      This code implements the API routes for two GET requests:

      • /feed: Returns a list of published posts.
      • /post/:id: Returns a specific post by its ID.

      Prisma Client is used in both implementations. In the /feed route implementation, the query you send with Prisma Client filters for all Post records where the published column contains the value true. Additionally, the Prisma Client query uses include to also fetch the related author information for each returned post. In the /post/:id route implementation, you are passing the ID that is retrieved from the URL’s path in order to read a specific Post record from the database.

      You can stop the server hitting CTRL+C on your keyboard. Then, restart the server using:

      To test the /feed route, you can use the following curl command:

      • curl http://localhost:3000/feed

      Since no posts have been published yet, the response is an empty array:

      Output

      []

      To test the /post/:id route, you can use the following curl command:

      • curl http://localhost:3000/post/1

      This will return the post you initially created:

      Output

      {"id":1,"title":"Hello World","content":null,"published":false,"authorId":1}

      Next, implement the two POST routes. Add the following code to index.ts following the implementations of the three GET routes:

      my-blog/src/index.ts

      . . .
      
      app.post(`/user`, async (req, res) => {
        const result = await prisma.user.create({
          data: { ...req.body },
        })
        res.json(result)
      })
      
      app.post(`/post`, async (req, res) => {
        const { title, content, authorEmail } = req.body
        const result = await prisma.post.create({
          data: {
            title,
            content,
            published: false,
            author: { connect: { email: authorEmail } },
          },
        })
        res.json(result)
      })
      
      app.listen(3000, () =>
        console.log('REST API server ready at: http://localhost:3000'),
      )
      

      Once you’re done, save and exit your file.

      This code implements the API routes for two POST requests:

      • /user: Creates a new user in the database.
      • /post: Creates a new post in the database.

      Like before, Prisma Client is used in both implementations. In the /user route implementation, you’re passing in the values from the body of the HTTP request to the Prisma Client create query.

      The /post route is a bit more involved: Here you can’t directly pass in the values from the body of the HTTP request; instead you first need to manually extract them to pass them to the Prisma Client query. The reason for this is that the structure of the JSON in the request body does not match the structure that’s expected by Prisma Client, so you need to manually create the expected structure.

      You can test the new routes by stopping the server with CTRL+C. Then, restart the server using:

      To create a new user via the /user route, you can send the following POST request with curl:

      • curl -X POST -H "Content-Type: application/json" -d '{"name":"Bob", "email":"[email protected]"}' http://localhost:3000/user

      This will create a new user in the database, printing the following output:

      Output

      {"id":2,"email":"[email protected]","name":"Bob"}

      To create a new post via the /post route, you can send the following POST request with curl:

      • curl -X POST -H "Content-Type: application/json" -d '{"title":"I am Bob", "authorEmail":"[email protected]"}' http://localhost:3000/post

      This will create a new post in the database and connect it to the user with the email [email protected]. It prints the following output:

      Output

      {"id":2,"title":"I am Bob","content":null,"published":false,"authorId":2}

      Finally, you can implement the PUT and DELETE routes.

      Open up index.ts with the following command:

      Next, following the implementation of the two POST routes, add the highlighted code:

      my-blog/src/index.ts

      . . .
      
      app.put('/post/publish/:id', async (req, res) => {
        const { id } = req.params
        const post = await prisma.post.update({
          where: { id: Number(id) },
          data: { published: true },
        })
        res.json(post)
      })
      
      app.delete(`/post/:id`, async (req, res) => {
        const { id } = req.params
        const post = await prisma.post.delete({
          where: { id: Number(id) },
        })
        res.json(post)
      })
      
      app.listen(3000, () =>
        console.log('REST API server ready at: http://localhost:3000'),
      )
      

      Save and exit your file.

      This code implements the API routes for one PUT and one DELETE request:

      • /post/publish/:id (PUT): Publishes a post by its ID.
      • /post/:id (DELETE): Deletes a post by its ID.

      Again, Prisma Client is used in both implementations. In the /post/publish/:id route implementation, the ID of the post to be published is retrieved from the URL and passed to the update query of Prisma Client. The implementation of the /post/:id route to delete a post in the database also retrieves the post ID from the URL and passes it to the delete query of Prisma Client.

      Again, stop the server with CTRL+C on your keyboard. Then, restart the server using:

      You can test the PUT route with the following curl command:

      • curl -X PUT http://localhost:3000/post/publish/2

      This is going to publish the post with an ID value of 2. If you resend the /feed request, this post will now be included in the response.

      Finally, you can test the DELETE route with the following curl command:

      • curl -X DELETE http://localhost:3000/post/1

      This is going to delete the post with an ID value of 1. To validate that the post with this ID has been deleted, you can resend a GET request to the /post/1 route.

      In this step, you implemented the remaining REST API routes for your blogging application. The API now responds to various GET, POST, PUT, and DELETE requests and implements functionality to read and write data in the database.

      Conclusion

      In this article, you created a REST API server with a number of different routes to create, read, update, and delete user and post data for a sample blogging application. Inside of the API routes, you are using the Prisma Client to send the respective queries to your database.

      As next steps, you can implement additional API routes or extend your database schema using Prisma Migrate. Be sure to visit the Prisma documentation to learn about different aspects of Prisma and explore some ready-to-run example projects in the prisma-examples repository—using tools such as GraphQL or grPC APIs.



      Source link

      Comprender desestructurar, los parámetros Rest y propagar sintaxis en JavaSript


      El autor seleccionó el COVID-19 Relief Fund para que reciba una donación como parte del programa Write for DOnations.

      Introducción

      Desde la Edición 2015 de la especificación ECMAScript, hay disponibles muchas nuevas funciones para el lenguaje JavaScript para trabajar con conjuntos y objetos. Algunas de las más notables que aprenderá en este artículo son desestructurar, parámetros rest y propagar sintaxis. Estas funciones ofrecen formas más directas de acceder a los miembros de una matriz o un objeto, y pueden hacer que trabajar con estas estructuras de datos sea más rápido y conciso.

      Muchos otros lenguajes no tienen la sintaxis correspondiente para desestructurar, los parámetros rest y propagar, de forma que estas funciones pueden tener una curva de aprendizaje para los nuevos desarrolladores de JavaScript y para aquellos que provienen de otro lenguaje. En este artículo, aprenderá cómo desestructurar objetos y conjuntos, cómo usar el operador de propagación para descomprimir objetos y conjuntos y cómo usar los parámetros rest en las invocaciones de funciones.

      Desestructurar

      La asignación de desestructurar es una sintaxis que le permite asignar propiedades de objetos o elementos de una matriz como variables. Esto puede reducir enormemente las líneas de código necesarias para manipular datos en estas estructuras. Existen dos tipos de desestructuración: desestructurar objetos y desestructurar conjuntos.

      Desestructurar objetos

      La desestructuración de objetos permite crear nuevas variables usando una propiedad de objeto como el valor.

      Considere este ejemplo, un objeto que representa una nota con id, title y date:

      const note = {
        id: 1,
        title: 'My first note',
        date: '01/01/1970',
      }
      

      Tradicionalmente, si quería crear una nueva variable para cada propiedad, tendría que asignar cada variable individualmente, con muchas repeticiones:

      // Create variables from the Object properties
      const id = note.id
      const title = note.title
      const date = note.date
      

      Con la desestructuración de objetos, esto puede hacerse en una línea. Al rodear cada variable entre corchetes {}, JavaScript creará nuevas variables desde cada propiedad con el mismo nombre:

      // Destructure properties into variables
      const { id, title, date } = note
      

      Ahora console.log() las nuevas variables:

      console.log(id)
      console.log(title)
      console.log(date)
      

      Obtendrá los valores originales de la propiedad como resultado:

      Output

      1 My first note 01/01/1970

      Nota: Desestructurar un objeto no modifica el objeto original. Aún podría invocar note original con todas sus entradas intactas.

      La asignación predeterminada para la desestructuración de objetos crea nuevas variables con el mismo nombre que la propiedad del objeto. Si no desea que la nueva variable tenga el mismo nombre que el nombre de la propiedad, tendrá la opción de cambiar el nombre de la nueva variable usando dos puntos (:) para decidir un nuevo nombre, como se puede ver con noteId a continuación:

      // Assign a custom name to a destructured value
      const { id: noteId, title, date } = note
      

      Registre la nueva variable noteId para la consola:

      console.log(noteId)
      

      Recibirá el siguiente resultado:

      Output

      1

      También puede desestructurar valores de objetos anidados. Por ejemplo, actualice el objeto note para tener un objeto author anidado:

      const note = {
        id: 1,
        title: 'My first note',
        date: '01/01/1970',
        author: {
          firstName: 'Sherlock',
          lastName: 'Holmes',
        },
      }
      

      Ahora puede desestructurar note, luego desestructurarla de nuevo para crear variables desde las propiedades author:

      // Destructure nested properties
      const {
        id,
        title,
        date,
        author: { firstName, lastName },
      } = note
      

      A continuación, registre las nuevas variables firstName y lastName usando los literales de plantilla:

      console.log(`${firstName} ${lastName}`)
      

      Esto generará el siguiente resultado:

      Output

      Sherlock Holmes

      Observe que en este ejemplo, aunque tiene acceso a los contenidos del objeto author, el objeto author en sí mismo no es accesible. Para acceder a un objeto y a sus valores anidados, tendría que declararlos por separado:

      // Access object and nested values
      const {
        author,
        author: { firstName, lastName },
      } = note
      
      console.log(author)
      

      Este código dará como resultado el objeto author:

      Output

      {firstName: "Sherlock", lastName: "Holmes"}

      Desestructurar un objeto no es solo útil para reducir la cantidad de código que tiene que escribir; también le permite orientar el acceso a las propiedades que necesita.

      Finalmente, la desestructuración puede usarse para acceder a las propiedades de los valores primitivos del objeto. Por ejemplo, String es un objeto global para cadenas, y tiene una propiedad length:

      const { length } = 'A string'
      

      Esto encontrará la propiedad de longitud inherente de una cadena y la establecerá igual a la variable length. Registre length para ver si funciona:

      console.log(length)
      

      Verá el siguiente resultado:

      Output

      8

      La cadena A string se convirtió específicamente en un objeto para recuperar la propiedad length.

      Desestructuración de conjuntos

      La desestructuración de conjuntos le permite crear nuevas variables usando un elemento de una matriz como valor. Considere este ejemplo, una matriz con las diferentes partes de una fecha:

      const date = ['1970', '12', '01']
      

      Se garantiza que los conjuntos en JavaScript conserven su orden, de forma que en este caso, el primer índice siempre será un año, el segundo será el mes y así sucesivamente. Sabiendo esto, puede crear variables a partir de los elementos de la matriz:

      // Create variables from the Array items
      const year = date[0]
      const month = date[1]
      const day = date[2]
      

      Pero hacer esto manualmente puede ocupar mucho espacio en su código. Con la desestructuración de matrices, puede descomprimir los valores de la matriz para ordenarlos y asignarlos a sus propias variables, de esta forma:

      // Destructure Array values into variables
      const [year, month, day] = date
      

      Ahora registre las nuevas variables:

      console.log(year)
      console.log(month)
      console.log(day)
      

      Verá el siguiente resultado:

      Output

      1970 12 01

      Los valores pueden omitirse dejando la sintaxis de desestructuración en blanco entre comas:

      // Skip the second item in the array
      const [year, , day] = date
      
      console.log(year)
      console.log(day)
      

      Al ejecutar esto obtendremos el valor de year y day:

      Output

      1970 01

      Los conjuntos anidados pueden ser desestructurados también. Primero cree una matriz anidada:

      // Create a nested array
      const nestedArray = [1, 2, [3, 4], 5]
      

      Luego, desestructure esa matriz y registre las nuevas variables:

      // Destructure nested items
      const [one, two, [three, four], five] = nestedArray
      
      console.log(one, two, three, four, five)
      

      Recibirá el siguiente resultado:

      Output

      1 2 3 4 5

      La sintaxis de desestructuración puede aplicarse para desestructurar los parámetros en una función. Para probar esto, desestructurará keys y values de Object.entries().

      Primero, declare el objeto note:

      const note = {
        id: 1,
        title: 'My first note',
        date: '01/01/1970',
      }
      

      Dado este objeto, podría listar los pares clave-valor desestructurando argumentos a medida que se pasan al método forEach():

      // Using forEach
      Object.entries(note).forEach(([key, value]) => {
        console.log(`${key}: ${value}`)
      })
      

      O podría conseguir lo mismo usando un bucle for:

      // Using a for loop
      for (let [key, value] of Object.entries(note)) {
        console.log(`${key}: ${value}`)
      }
      

      De cualquier forma, recibirá lo siguiente:

      Output

      id: 1 title: My first note date: 01/01/1970

      La desestructuración de objetos y la desestructuración de conjuntos pueden combinarse en una única asignación de desestructuración. También pueden usarse parámetros predeterminados con la desestructuración, como se ve en este ejemplo que establece la fecha predeterminada a new Date().

      Primero, declare el objeto note:

      const note = {
        title: 'My first note',
        author: {
          firstName: 'Sherlock',
          lastName: 'Holmes',
        },
        tags: ['personal', 'writing', 'investigations'],
      }
      

      Luego desestructure el objeto, a la vez que establece una nueva variable date con el predeterminado de new Date():

      const {
        title,
        date = new Date(),
        author: { firstName },
        tags: [personalTag, writingTag],
      } = note
      
      console.log(date)
      

      console.log(date) proporcionará un resultado similar al siguiente:

      Output

      Fri May 08 2020 23:53:49 GMT-0500 (Central Daylight Time)

      Como se muestra en esta sección, la sintaxis de asignación de desestructuración añade mucha flexibilidad a JavaScript y le permite escribir un código más conciso. En la siguiente sección, verá cómo propagar sintaxis puede usarse para expandir las estructuras de datos en sus entradas de datos constituyentes.

      Propagación

      Propagar sintaxis (...) es otra adición útil a JavaScript para trabajar con conjuntos, objetos e invocaciones de función. La propagación permite descomprimir o expandir objetos e iterables (como los conjuntos), lo que puede ser usado para realizar copias superficiales de estructura de datos para aumentar la facilidad de la manipulación de datos.

      Propagación con conjuntos

      La propagación puede simplificar las tareas comunes con los conjuntos. Por ejemplo, digamos que tiene dos conjuntos y quiere combinarlos:

      // Create an Array
      const tools = ['hammer', 'screwdriver']
      const otherTools = ['wrench', 'saw']
      

      Originalmente, usaría concat() para concatenar los dos conjuntos:

      // Concatenate tools and otherTools together
      const allTools = tools.concat(otherTools)
      

      Ahora también puede propagar para descomprimir las matrices en un nueva matriz:

      // Unpack the tools Array into the allTools Array
      const allTools = [...tools, ...otherTools]
      
      console.log(allTools)
      

      Al ejecutar esto obtendrá lo siguiente:

      Output

      ["hammer", "screwdriver", "wrench", "saw"]

      Esto puede ser particularmente útil con la inmutabilidad. Por ejemplo, es posible que esté trabajando con una aplicación que tiene users guardados en una matriz de objetos:

      // Array of users
      const users = [
        { id: 1, name: 'Ben' },
        { id: 2, name: 'Leslie' },
      ]
      

      Podría usar push para modificar la matriz existente y añadir un nuevo usuario, que será una opción mutable:

      // A new user to be added
      const newUser = { id: 3, name: 'Ron' }
      
      users.push(newUser)
      

      Pero esto cambia la matriz user, que quizá queramos conservar.

      La propagación le permite crear una nueva matriz partir de una existente y añadir un nuevo elemento al final:

      const updatedUsers = [...users, newUser]
      
      console.log(users)
      console.log(updatedUsers)
      

      Ahora, la nueva matriz updatedUsers tiene el nuevo usuario, pero la matriz original users permanece sin cambios:

      Output

      [{id: 1, name: "Ben"} {id: 2, name: "Leslie"}] [{id: 1, name: "Ben"} {id: 2, name: "Leslie"} {id: 3, name: "Ron"}]

      Crear copias de datos en vez de cambiar los datos existentes puede ayudar a prevenir cambios inesperados. En JavaScript, cuando crea un objeto o matriz y lo asigna a otra variable, no está creando realmente un nuevo objeto, está pasando una referencia.

      Tome este ejemplo, en el cual se crea una matriz y se asigna a otra variable:

      // Create an Array
      const originalArray = ['one', 'two', 'three']
      
      // Assign Array to another variable
      const secondArray = originalArray
      

      Eliminar el último elemento de la segunda matriz modificará el primero:

      // Remove the last item of the second Array
      secondArray.pop()
      
      console.log(originalArray)
      

      Esto generará el siguiente resultado:

      Output

      ["one", "two"]

      La propagación le permite realizar una copia superficial de una matriz u objeto, lo que significa que cualquier propiedad de nivel superior se clonará, pero los objetos anidados se pasarán por referencia. Para conjuntos u objetos simples, una copia superficial puede ser todo lo que necesita.

      Si escribe el mismo código de ejemplo pero copia la matriz con propagación, la matriz original no se modificará:

      // Create an Array
      const originalArray = ['one', 'two', 'three']
      
      // Use spread to make a shallow copy
      const secondArray = [...originalArray]
      
      // Remove the last item of the second Array
      secondArray.pop()
      
      console.log(originalArray)
      

      Lo siguiente se registrará en la consola:

      Output

      ["one", "two", "three"]

      La propagación también puede usarse para convertir un conjunto, o cualquier otro iterable, a una matriz.

      Crear un nuevo conjunto y añadir algunas entradas:

      // Create a set
      const set = new Set()
      
      set.add('octopus')
      set.add('starfish')
      set.add('whale')
      

      A continuación, utilice el operador de propagación con set y registre los resultados:

      // Convert Set to Array
      const seaCreatures = [...set]
      
      console.log(seaCreatures)
      

      Esto dará el siguiente resultado:

      Output

      ["octopus", "starfish", "whale"]

      Esto puede ser útil para crear un conjunto desde una cadena:

      const string = 'hello'
      
      const stringArray = [...string]
      
      console.log(stringArray)
      

      Esto proporcionará una matriz con cada carácter como un elemento en la matriz:

      Output

      ["h", "e", "l", "l", "o"]

      Propagación con objetos

      Cuando se trabaja con objetos, la propagación puede usarse para copiar y actualizar objetos.

      Originalmente, Object.assign() se usó para copiar un objeto:

      // Create an Object and a copied Object with Object.assign()
      const originalObject = { enabled: true, darkMode: false }
      const secondObject = Object.assign({}, originalObject)
      

      El secondObject ahora será un clon del originalObject.

      Esto se simplifica con la sintaxis de propagación; puede copiar superficialmente un objeto propagándolo a uno nuevo:

      // Create an object and a copied object with spread
      const originalObject = { enabled: true, darkMode: false }
      const secondObject = { ...originalObject }
      
      console.log(secondObject)
      

      Esto dará como resultado lo siguiente:

      Output

      {enabled: true, darkMode: false}

      Igual que con las matrices, esto solo creará una copia superficial y los objetos anidados seguirán pasándose por referencia.

      Añadir o modificar propiedades sobre un objeto existente de forma inmutable se simplifica con la propagación. En este ejemplo, la propiedad isLoggedIn se añade al objeto user:

      const user = {
        id: 3,
        name: 'Ron',
      }
      
      const updatedUser = { ...user, isLoggedIn: true }
      
      console.log(updatedUser)
      

      Esto dará el siguiente resultado:

      Output

      {id: 3, name: "Ron", isLoggedIn: true}

      Algo importante a tener en cuenta a la hora de actualizar objetos mediante la propagación es que cualquier objeto anidado también se propagará. Por ejemplo, digamos que en el objeto user hay un objeto anidado organization:

      const user = {
        id: 3,
        name: 'Ron',
        organization: {
          name: 'Parks & Recreation',
          city: 'Pawnee',
        },
      }
      

      Si intentase añadir un nuevo elemento a organization, sobrescribiría los campos existentes:

      const updatedUser = { ...user, organization: { position: 'Director' } }
      
      console.log(updatedUser)
      

      Esto resultará en lo siguiente:

      Output

      id: 3 name: "Ron" organization: {position: "Director"}

      Si la mutabilidad no es un problema, el campo podría actualizarse directamente:

      user.organization.position = 'Director'
      

      Pero ya que estamos buscando una solución inmutable, podemos propagar el objeto interno para que conserve las propiedades existentes:

      const updatedUser = {
        ...user,
        organization: {
          ...user.organization,
          position: 'Director',
        },
      }
      
      console.log(updatedUser)
      

      Esto dará el siguiente resultado:

      Output

      id: 3 name: "Ron" organization: {name: "Parks & Recreation", city: "Pawnee", position: "Director"}

      Propagación con invocaciones de función

      La propagación también puede usarse con argumentos en las invocaciones de funciones.

      Como ejemplo, aquí hay una función multiply que toma tres parámetros y los multiplica:

      // Create a function to multiply three items
      function multiply(a, b, c) {
        return a * b * c
      }
      

      Normalmente, pasaría tres valores individualmente como argumentos a la invocación de función, de esta forma:

      multiply(1, 2, 3)
      

      El resultado sería el siguiente:

      Output

      6

      Sin embargo, si todos los valores que desea pasar a la función ya existen en una matriz, la propagación de sintaxis le permite usar cada elemento en una matriz como argumento:

      const numbers = [1, 2, 3]
      
      multiply(...numbers)
      

      Esto dará el mismo resultado:

      Output

      6

      Nota: Sin propagación, esto puede conseguirse usando apply():

      multiply.apply(null, [1, 2, 3])
      

      Esto proporcionará lo siguiente:

      Output

      6

      Ahora que ha visto cómo la propagación puede acortar su código, puede echar un vistazo a un uso diferente de la sintaxis ...: parámetros rest.

      Parámetros rest

      La última función que aprenderá en este artículo es la sintaxis parámetro rest. La sintaxis parece igual a medida que se propaga (...), pero tiene el efecto contrario. En vez de descomprimir una matriz u objeto en valores individuales, la sintaxis rest creará una matriz de un número indefinido de argumentos.

      En la función restTest, por ejemplo, si queremos que args sea una matriz compuesta de un número indefinido de argumentos, podrías tener lo siguiente:

      function restTest(...args) {
        console.log(args)
      }
      
      restTest(1, 2, 3, 4, 5, 6)
      

      Todos los argumentos pasados a la función restTest están ahora disponibles en la matriz args:

      Output

      [1, 2, 3, 4, 5, 6]

      La sintaxis rest puede usarse como el único parámetro o como el último parámetro en la lista. Si se utiliza como el único parámetro, recopilará todos los argumentos, pero si está al final de una lista, recopilará todos los argumentos que quedan, como se ve en este ejemplo:

      function restTest(one, two, ...args) {
        console.log(one)
        console.log(two)
        console.log(args)
      }
      
      restTest(1, 2, 3, 4, 5, 6)
      

      Esto tomará los dos primeros argumentos individualmente, y luego agrupará el resto en una matriz:

      Output

      1 2 [3, 4, 5, 6]

      El código anterior, la variable arguments podría usarse para recopilar todos los argumentos pasados a través de una función:

      function testArguments() {
        console.log(arguments)
      }
      
      testArguments('how', 'many', 'arguments')
      

      Esto nos dará el siguiente resultado:

      Output

      1Arguments(3) ["how", "many", "arguments"]

      Sin embargo, esto tiene algunas desventajas. Primero, la variable arguments no puede usarse con las funciones de flecha.

      const testArguments = () => {
        console.log(arguments)
      }
      
      testArguments('how', 'many', 'arguments')
      

      Esto resultaría en un error:

      Output

      Uncaught ReferenceError: arguments is not defined

      Adicionalmente, arguments no es una matriz verdadera y no puede usar métodos como map y filter sin convertirse primero a una matriz. También recogerá todos los argumentos pasados de solo el resto de los argumentos, como se ve en el ejemplo restTest(one, two, ...args).

      Rest puede usarse cuando se desestructuran las matrices también:

      const [firstTool, ...rest] = ['hammer', 'screwdriver', 'wrench']
      
      console.log(firstTool)
      console.log(rest)
      

      Esto proporcionará lo siguiente:

      Output

      hammer ["screwdriver", "wrench"]

      Rest también puede usarse cuando se desestructuran objetos:

      const { isLoggedIn, ...rest } = { id: 1, name: 'Ben', isLoggedIn: true }
      
      console.log(isLoggedIn)
      console.log(rest)
      

      Proporcionar el siguiente resultado:

      Output

      true {id: 1, name: "Ben"}

      De esta forma, la sintaxis rest proporciona métodos eficientes para recopilar una cantidad indeterminada de elementos.

      Conclusión

      En este artículo, ha aprendido cómo desestructurar, propagar sintaxis y sobre los parámetros rest. En resumen:

      • Desestructurar se usa para crear variables a partir de los elementos de una matriz o de propiedades de objetos.
      • Propagar sintaxis se usa para descomprimir iterables como matrices, objetos e invocaciones de función.
      • La sintaxis del parámetro rest creará una matriz a partir de un número indefinido de valores.

      Desestructurar, parámetros rest y propagar sintaxis son funciones útiles en JavaScript que ayudan a mantener su código conciso y limpio.

      Si desea ver la desestructuración en acción, eche un vistazo a Cómo personalizar componentes React con Props, que utiliza esta sintaxis para desestructurar datos y pasarlos a componentes front-end personalizados. Si desea aprender más sobre JavaScript, vuelva a nuestra página de la serie Cómo codificar en JavaScript.



      Source link

      Entendendo as sintaxes de desestruturação, parâmetros rest e espalhamento em JavaScript


      O autor selecionou a COVID-19 Relief Fund​​​​​ para receber uma doação como parte do programa Write for DOnations.

      Introdução

      Muitas novas funcionalidades para trabalhar com matrizes e objetos foram disponibilizadas para a linguagem JavaScript desde a Edição de 2015 da especificação ECMAScript. Alguns dos elementos notáveis que você aprenderá neste artigo são a desestruturação, os parâmetros rest e a sintaxe de espalhamento. Esses recursos nos dão maneiras mais diretas de acessar os membros de uma matriz ou objeto, e podem tornar o trabalho com essas estruturas de dados mais rápido e sucinto.

      Muitas outras linguagens não possuem sintaxes correspondentes para a desestruturação, parâmetros rest e espalhamento. Por este motivo, esses recursos podem ter uma curva de aprendizado tanto para novos desenvolvedores JavaScript quanto para aqueles que estejam vindo de outra linguagem. Neste artigo, você aprenderá como desestruturar objetos e matrizes, como usar o operador de espalhamento para descompactar objetos e matrizes e como usar os parâmetros rest em chamadas de função.

      Desestruturação

      A atribuição de desestruturação é uma sintaxe que permite que você atribua propriedades de objetos ou itens de matrizes como variáveis. Isso pode reduzir de maneira significativa as linhas de código necessárias para manipular dados nessas estruturas. Existem dois tipos de desestruturação: a desestruturação de objetos e a desestruturação de matrizes.

      Desestruturação de objetos

      A desestruturação de objetos permite que você crie novas variáveis usando uma propriedade de objeto como o valor.

      Considere este exemplo, onde um objeto que representa uma nota com um id, title e date:

      const note = {
        id: 1,
        title: 'My first note',
        date: '01/01/1970',
      }
      

      Tradicionalmente, se você quisesse criar uma nova variável para cada propriedade, você teria que atribuir cada variável individualmente, com muitas repetições:

      // Create variables from the Object properties
      const id = note.id
      const title = note.title
      const date = note.date
      

      Com a desestruturação de objetos, tudo isso pode ser feito em uma linha. Ao colocar cada variável entre chaves {}, o JavaScript criará novas variáveis a partir de cada propriedade com o mesmo nome:

      // Destructure properties into variables
      const { id, title, date } = note
      

      Agora, use o console.log() nas novas variáveis:

      console.log(id)
      console.log(title)
      console.log(date)
      

      Você receberá os valores originais das propriedades como resultado:

      Output

      1 My first note 01/01/1970

      Nota: a desestruturação de objetos não modifica o objeto original. Você ainda pode chamar a note (nota) original com todas as entradas intactas.

      A atribuição padrão para a desestruturação de objetos cria novas variáveis com o mesmo nome que a propriedade do objeto. Se você não quiser que a nova variável tenha o mesmo nome que o nome da propriedade, você também tem a opção de renomear a nova variável usando dois pontos (:) para decidir um novo nome, como visto com o noteId no seguinte exemplo:

      // Assign a custom name to a destructured value
      const { id: noteId, title, date } = note
      

      Registre a nova variável noteId no console:

      console.log(noteId)
      

      Você receberá o seguinte resultado:

      Output

      1

      Você também pode desestruturar valores aninhados de objetos. Por exemplo, atualize o objeto note para que tenha um objeto aninhado author:

      const note = {
        id: 1,
        title: 'My first note',
        date: '01/01/1970',
        author: {
          firstName: 'Sherlock',
          lastName: 'Holmes',
        },
      }
      

      Agora, você pode desestruturar o note, então desestruturar novamente para criar variáveis a partir das propriedades de author:

      // Destructure nested properties
      const {
        id,
        title,
        date,
        author: { firstName, lastName },
      } = note
      

      Em seguida, registre as novas variáveis firstName e lastName usando os template literals:

      console.log(`${firstName} ${lastName}`)
      

      Isso dará o seguinte resultado:

      Output

      Sherlock Holmes

      Observe que neste exemplo, embora você tenha acesso ao conteúdo do objeto author, o objeto author em si não está acessível. Para acessar um objeto, bem como seus valores aninhados, você teria que declará-los separadamente:

      // Access object and nested values
      const {
        author,
        author: { firstName, lastName },
      } = note
      
      console.log(author)
      

      Este código irá gerar o objeto author como resultado:

      Output

      {firstName: "Sherlock", lastName: "Holmes"}

      Desestruturar um objeto é útil não apenas para reduzir a quantidade de código que você precisa escrever, mas também permite que você mire seu acesso nas propriedades que julga importantes.

      Por fim, a desestruturação pode ser utilizada para acessar as propriedades de objetos de valores primitivos. Por exemplo, String é um objeto global para strings e possui uma propriedade length (comprimento):

      const { length } = 'A string'
      

      Isso encontrará a propriedade de comprimento inerente de uma string e a definirá como sendo igual à variável length. Registre length para ver se o processo funcionou:

      console.log(length)
      

      Você receberá o seguinte resultado:

      Output

      8

      Aqui, a string A string foi implicitamente convertida em um objeto para recuperar a propriedade length.

      Desestruturação de matrizes

      A desestruturação de matrizes permite que você crie novas variáveis usando um item de uma matriz como valor. Considere este exemplo, onde há uma matriz com as várias partes de uma data:

      const date = ['1970', '12', '01']
      

      As matrizes em JavaScript garantem a preservação de sua ordem. Dessa forma, neste caso, o primeiro índice será sempre um ano, o segundo será o mês e assim por diante. Sabendo isso, você pode criar variáveis a partir dos itens da matriz:

      // Create variables from the Array items
      const year = date[0]
      const month = date[1]
      const day = date[2]
      

      Apesar disso, fazer isso manualmente pode tomar muito espaço do seu código. Com a desestruturação da matriz, você pode descompactar os valores da matriz em ordem e atribuí-los às suas próprias variáveis, desta forma:

      // Destructure Array values into variables
      const [year, month, day] = date
      

      Agora, registre as novas variáveis:

      console.log(year)
      console.log(month)
      console.log(day)
      

      Você receberá o seguinte resultado:

      Output

      1970 12 01

      Os valores podem ser ignorados deixando a sintaxe de desestruturação em branco entre vírgulas:

      // Skip the second item in the array
      const [year, , day] = date
      
      console.log(year)
      console.log(day)
      

      Ao executar isso, você receberá o valor de year (ano) e day (dia):

      Output

      1970 01

      As matrizes aninhadas também podem ser desestruturadas. Primeiro, crie uma matriz aninhada:

      // Create a nested array
      const nestedArray = [1, 2, [3, 4], 5]
      

      Em seguida, desestruture aquela matriz e registre as novas variáveis:

      // Destructure nested items
      const [one, two, [three, four], five] = nestedArray
      
      console.log(one, two, three, four, five)
      

      Você receberá o seguinte resultado:

      Output

      1 2 3 4 5

      A sintaxe de desestruturação pode ser aplicada na desestruturação dos parâmetros em uma função. Para testar isso, você irá desestruturar as keys (chaves) e values (valores) do Object.entries().

      Primeiro, declare o objeto de note:

      const note = {
        id: 1,
        title: 'My first note',
        date: '01/01/1970',
      }
      

      Dado este objeto, você poderia listar os pares de chave de valor através da desestruturação de argumentos à medida em que eles são passados ao método forEach():

      // Using forEach
      Object.entries(note).forEach(([key, value]) => {
        console.log(`${key}: ${value}`)
      })
      

      Ou você poderia alcançar o mesmo resultado usando um loop for:

      // Using a for loop
      for (let [key, value] of Object.entries(note)) {
        console.log(`${key}: ${value}`)
      }
      

      De qualquer maneira, você receberá o seguinte:

      Output

      id: 1 title: My first note date: 01/01/1970

      A desestruturação de objetos e de matrizes podem ser combinadas em uma única atribuição de desestruturação. Os parâmetros padrão também podem ser usados com a desestruturação, como visto no exemplo a seguir que define a data como new Date().

      Primeiro, declare o objeto de note:

      const note = {
        title: 'My first note',
        author: {
          firstName: 'Sherlock',
          lastName: 'Holmes',
        },
        tags: ['personal', 'writing', 'investigations'],
      }
      

      Em seguida, desestruture o objeto, ao mesmo tempo em que você também define uma nova variável date com o padrão de new Date():

      const {
        title,
        date = new Date(),
        author: { firstName },
        tags: [personalTag, writingTag],
      } = note
      
      console.log(date)
      

      Então, o console.log(date) gerará um resultado semelhante ao seguinte:

      Output

      Fri May 08 2020 23:53:49 GMT-0500 (Central Daylight Time)

      Como mostrado nesta seção, a sintaxe de atribuição de desestruturação adiciona muita flexibilidade ao JavaScript e permite que você escreva códigos mais sucintos. Na próxima seção, você verá como a sintaxe de espalhamento pode ser utilizada para expandir estruturas de dados nas entradas de dados constituintes.

      Espalhamento

      A sintaxe de espalhamento (...) é outra adição ao JavaScript bastante útil para trabalhar com matrizes, objetos e chamadas de função. O espalhamento permite que objetos e iteráveis (como matrizes) sejam descompactados ou expandidos. Isso pode ser usado para fazer cópias superficiais de estruturas de dados para facilitar a manipulação de dados.

      Espalhamento com matrizes

      O espalhamento simplifica as tarefas comuns com matrizes. Por exemplo, vamos supor que você tenha duas matrizes e deseja combiná-las:

      // Create an Array
      const tools = ['hammer', 'screwdriver']
      const otherTools = ['wrench', 'saw']
      

      Originalmente, você usaria o concat() para concatenar as duas matrizes:

      // Concatenate tools and otherTools together
      const allTools = tools.concat(otherTools)
      

      Agora, você também pode usar o espalhamento para descompactar as matrizes em uma nova matriz:

      // Unpack the tools Array into the allTools Array
      const allTools = [...tools, ...otherTools]
      
      console.log(allTools)
      

      Executar isso resultaria no seguinte:

      Output

      ["hammer", "screwdriver", "wrench", "saw"]

      Isso pode ser particularmente útil com a imutabilidade. Por exemplo, você poderia estar trabalhando com um app que possui users (usuários) armazenados em uma matriz de objetos:

      // Array of users
      const users = [
        { id: 1, name: 'Ben' },
        { id: 2, name: 'Leslie' },
      ]
      

      Você poderia usar o push para modificar a matriz existente e adicionar um novo usuário, o que seria a opção mutável:

      // A new user to be added
      const newUser = { id: 3, name: 'Ron' }
      
      users.push(newUser)
      

      Mas isso altera a matriz user, que pode ser que queiramos preservar.

      O espalhamento permite que você crie uma nova matriz a partir de uma existente e adicione um novo item no final:

      const updatedUsers = [...users, newUser]
      
      console.log(users)
      console.log(updatedUsers)
      

      Agora, a nova matriz updatedUsers possui o novo usuário, mas a matriz users original permanece inalterada:

      Output

      [{id: 1, name: "Ben"} {id: 2, name: "Leslie"}] [{id: 1, name: "Ben"} {id: 2, name: "Leslie"} {id: 3, name: "Ron"}]

      Criar cópias de dados em vez de alterar dados existentes ajuda a evitar alterações inesperadas. Em JavaScript, quando você cria um objeto ou matriz e a atribui a outra variável, você não está criando, de fato, um novo objeto — você está passando uma referência.

      Observe este exemplo, onde uma matriz é criada e atribuída a outra variável:

      // Create an Array
      const originalArray = ['one', 'two', 'three']
      
      // Assign Array to another variable
      const secondArray = originalArray
      

      Remover o último item da segunda matriz modificará o primeiro:

      // Remove the last item of the second Array
      secondArray.pop()
      
      console.log(originalArray)
      

      Isso dará o resultado:

      Output

      ["one", "two"]

      O espalhamento permite que você crie uma cópia superficial de uma matriz ou objeto. Isso significa que qualquer propriedade de nível superior será clonada, mas objetos aninhados ainda serão passados por referência. Para matrizes ou objetos simples, uma cópia superficial pode ser tudo o que você precisa.

      Se você escrever o mesmo código de exemplo, mas copiar a matriz com o espalhamento, a matriz original não será mais modificada:

      // Create an Array
      const originalArray = ['one', 'two', 'three']
      
      // Use spread to make a shallow copy
      const secondArray = [...originalArray]
      
      // Remove the last item of the second Array
      secondArray.pop()
      
      console.log(originalArray)
      

      O seguinte ficará registrado no console:

      Output

      ["one", "two", "three"]

      O espalhamento também pode ser usado para converter um conjunto, ou qualquer outro iterável em uma matriz.

      Crie um novo conjunto e adicione algumas entradas a ele:

      // Create a set
      const set = new Set()
      
      set.add('octopus')
      set.add('starfish')
      set.add('whale')
      

      Em seguida, utilize o operador de espalhamento com o set (conjunto) e registre os resultados:

      // Convert Set to Array
      const seaCreatures = [...set]
      
      console.log(seaCreatures)
      

      Isso resultará no seguinte:

      Output

      ["octopus", "starfish", "whale"]

      Isso também pode ser útil para criar uma matriz a partir de uma string:

      const string = 'hello'
      
      const stringArray = [...string]
      
      console.log(stringArray)
      

      Isso resultará em uma matriz com cada caractere sendo um item na matriz:

      Output

      ["h", "e", "l", "l", "o"]

      Espalhamento com objetos

      Ao trabalhar com objetos, o espalhamento pode ser usado para copiar e atualizar objetos.

      Originalmente, o Object.assign() era usado para copiar um objeto:

      // Create an Object and a copied Object with Object.assign()
      const originalObject = { enabled: true, darkMode: false }
      const secondObject = Object.assign({}, originalObject)
      

      O secondObject será agora um clone do originalObject.

      Isso é simplificado com a sintaxe de espalhamento — você pode copiar um objeto superficialmente, espalhando-o em um novo:

      // Create an object and a copied object with spread
      const originalObject = { enabled: true, darkMode: false }
      const secondObject = { ...originalObject }
      
      console.log(secondObject)
      

      Isso dará como resultado o seguinte:

      Output

      {enabled: true, darkMode: false}

      Assim como com matrizes, isso criará apenas uma cópia superficial e objetos aninhados ainda serão passados por referência.

      Adicionar ou modificar propriedades em um objeto existente de maneira imutável torna-se mais simplificado com o espalhamento. Neste exemplo, a propriedade isLoggedIn é adicionada ao objeto user:

      const user = {
        id: 3,
        name: 'Ron',
      }
      
      const updatedUser = { ...user, isLoggedIn: true }
      
      console.log(updatedUser)
      

      Isso irá mostrar o seguinte:

      Output

      {id: 3, name: "Ron", isLoggedIn: true}

      Uma coisa importante a se notar com a atualização de objetos através do espalhamento é que qualquer objeto aninhado também terá que ser espalhado. Por exemplo, vamos supor que no objeto user existe um objeto organization aninhado:

      const user = {
        id: 3,
        name: 'Ron',
        organization: {
          name: 'Parks & Recreation',
          city: 'Pawnee',
        },
      }
      

      Se você tentasse adicionar um novo item ao organization, ele substituiria os campos existentes:

      const updatedUser = { ...user, organization: { position: 'Director' } }
      
      console.log(updatedUser)
      

      Isso resultaria no seguinte:

      Output

      id: 3 name: "Ron" organization: {position: "Director"}

      Se a mutabilidade não for um problema, o campo poderia ser atualizado diretamente:

      user.organization.position = 'Director'
      

      Mas como estamos buscando uma solução imutável, podemos espalhar o objeto interno para reter as propriedades existentes:

      const updatedUser = {
        ...user,
        organization: {
          ...user.organization,
          position: 'Director',
        },
      }
      
      console.log(updatedUser)
      

      Isso resultará no seguinte:

      Output

      id: 3 name: "Ron" organization: {name: "Parks & Recreation", city: "Pawnee", position: "Director"}

      Espalhamento com chamadas de função

      O espalhamento também pode ser usado com argumentos em chamadas de função.

      Como um exemplo, aqui está uma função multiply que recebe três parâmetros e os multiplica:

      // Create a function to multiply three items
      function multiply(a, b, c) {
        return a * b * c
      }
      

      Normalmente, você passaria três valores individualmente como argumentos para a chamada de função, desta forma:

      multiply(1, 2, 3)
      

      Isso resultaria no seguinte:

      Output

      6

      No entanto, se todos os valores que você deseja passar para a função já existirem em uma matriz, a sintaxe de espalhamento permitirá que você utilize cada item em uma matriz como um argumento:

      const numbers = [1, 2, 3]
      
      multiply(...numbers)
      

      Isso gerará o mesmo resultado:

      Output

      6

      Nota: sem o espalhamento, isso pode ser feito usando o apply():

      multiply.apply(null, [1, 2, 3])
      

      Isso dará:

      Output

      6

      Agora que você viu como o espalhamento pode encurtar seu código, dê uma olhada em um outro uso da sintaxe ...: os parâmetros rest.

      Parâmetros rest

      O último recurso que você aprenderá neste artigo é a sintaxe do parâmetro rest. A sintaxe aparece da mesma forma que o espalhamento (...) mas possui o efeito oposto. Ao invés de descompactar uma matriz ou objeto em valores individuais, a sintaxe do rest criará uma matriz de um número de argumentos indefinido.

      Na função restTest por exemplo, se quiséssemos que o args fosse uma matriz composta por um número de argumentos indefinido, poderíamos ter o seguinte:

      function restTest(...args) {
        console.log(args)
      }
      
      restTest(1, 2, 3, 4, 5, 6)
      

      Todos os argumentos passados para a função restTest estão agora disponíveis na matriz args:

      Output

      [1, 2, 3, 4, 5, 6]

      A sintaxe rest pode ser usada como o único parâmetro ou como o último parâmetro na lista. Se usada como único parâmetro, ela reunirá todos os argumentos. No entanto, se for usada no final de uma lista, ela reunirá todos os argumentos remanescentes, como visto neste exemplo:

      function restTest(one, two, ...args) {
        console.log(one)
        console.log(two)
        console.log(args)
      }
      
      restTest(1, 2, 3, 4, 5, 6)
      

      Isso pegará os dois primeiros argumentos individualmente. Em seguida, agrupará o restante em uma matriz:

      Output

      1 2 [3, 4, 5, 6]

      Em código mais antigo, a variável arguments poderia ser usada para reunir todos os argumentos passados para uma função:

      function testArguments() {
        console.log(arguments)
      }
      
      testArguments('how', 'many', 'arguments')
      

      O resultaria no seguinte:

      Output

      1Arguments(3) ["how", "many", "arguments"]

      No entanto, isso possui algumas desvantagens. Primeiro, a variável arguments não pode ser usada com funções de flecha.

      const testArguments = () => {
        console.log(arguments)
      }
      
      testArguments('how', 'many', 'arguments')
      

      Isso geraria um erro:

      Output

      Uncaught ReferenceError: arguments is not defined

      Além disso, arguments não é uma matriz verdadeira e não pode usar métodos como map e filter sem que seja primeiro convertida em uma matriz. Ela também irá coletar todos os argumentos passados em vez de apenas o restante dos argumentos, como visto no exemplo restTest(one, two, ...args).

      Os parâmetros rest também podem ser usados na desestruturação de matrizes:

      const [firstTool, ...rest] = ['hammer', 'screwdriver', 'wrench']
      
      console.log(firstTool)
      console.log(rest)
      

      Isso dará:

      Output

      hammer ["screwdriver", "wrench"]

      Os parâmetros rest também podem ser usados na desestruturação de objetos:

      const { isLoggedIn, ...rest } = { id: 1, name: 'Ben', isLoggedIn: true }
      
      console.log(isLoggedIn)
      console.log(rest)
      

      Gerando o seguinte resultado:

      Output

      true {id: 1, name: "Ben"}

      Desta forma, a sintaxe rest fornece métodos eficientes para reunir uma quantidade indeterminada de itens.

      Conclusão

      Neste artigo, você aprendeu sobre desestruturação, sintaxe de espalhamento e parâmetros rest. Resumindo:

      • A desestruturação é utilizada para criar variáveis a partir dos itens de matrizes ou propriedades de objetos.
      • A sintaxe de espalhamento é usada para descompactar iteráveis como matrizes, objetos e chamadas de função.
      • A sintaxe do parâmetro rest criará uma matriz a partir de um número indefinido de valores.

      As sintaxes de desestruturação, parâmetros rest e espalhamento são recursos úteis no JavaScript que ajudam a manter seu código sucinto e limpo.

      Se você quiser ver a desestruturação em ação, dê uma olhada em Como personalizar componentes React com o Props, que utiliza essa sintaxe para desestruturar dados e passá-los para componentes personalizados de front-end. Se você quiser aprender mais sobre o JavaScript, retorne para nossa página da série Como programar em JavaScript.



      Source link