Опционалното управление на Swift идва в JavaScript

Опционалното свързване е предложена функция, която може да бъде включена в спецификацията на JavaScript.

Операторът ви позволява да преминете през вложен обект, за да получите стойността на променливите, без да се притеснявате дали някоя от тях ще бъде недефинирана.

Например, без опционално свързване, ако имате обекта по-долу:

const person = {
  name: 'Alice',
  cat: {
    name: 'Bob'
  }
};

Ако искате да получите името на котката, трябва да използвате кода по-долу:

const catName = person.cat.name;

Ако cat е недефинирано или нула в person, интерпретаторът на JavaScript ще изведе грешка. С незадължителния оператор за верига можете да напишете:

const catName = person?.cat?.name;

Ако cat е недефинирано, catName ще бъде null.

Работи и с ключове на обект. Вместо const catName = person?.cat?.name;, можем да напишем:

const catName = person?.['cat']?.['name'];

Този синтаксис работи и с функции. Например можете да напишете:

func?.('foo')

За извикване на функцията func с низ, където func може да е недефиниран или нула. Ако функцията не съществува, тя няма да бъде стартирана.

За да илюстрираме допълнително примера и да ви покажем как да го използвате в реално приложение, ще създадем приложение React, което използва NASA API, за да получи най-новите данни за астероиди.

Ще използваме CLI програмата Create React App за създаване на приложението.

Тъй като незадължителното верижно свързване е само предложена функция, то в момента не се поддържа от CLI, така че трябва да свършим малко работа, като инсталираме някои пакети и направим някои промени в конфигурацията на Babel на приложението, за да активираме опционалното верижно свързване.

За начало стартираме npx create-react-app nasa-app, за да създадем папката на проекта с първоначалните файлове.

След това инсталираме npm i -D @babel/plugin-proposal-optional-chaining customize-cra react-app-rewired, за да започнем да персонализираме Create React App, за да поддържа незадължителния синтаксис за верижно свързване.

След това трябва да добавим нови конфигурационни файлове и да редактираме съществуващите, за да позволим на нашето приложение да работи и изгражда със синтаксиса.

Първо добавяме файл с име config-overrides.js и добавяме следното:

const { useBabelRc, override, useEslintRc } = require("customize-cra");
module.exports = override(useBabelRc());

За да ни позволи да използваме конфигурационния файл .babelrc.

След това създаваме файла .babelrc в основната папка на нашия проект и добавяме:

{
    "plugins": [
        [
            "@babel/plugin-proposal-optional-chaining"
        ],
    ]
}

Това ще добави поддръжка за незадължителен синтаксис за верижно свързване в нашия проект.

След това трябва да превключим, за да накараме нашето приложение да се изпълнява и изгражда с react-app-rewired вместо обичайната програма react-script.

За да направите това, в секцията scripts на package.json поставяме:

"scripts": {
    "start": "react-app-rewired start",
    "build": "react-app-rewired build",
    "test": "react-app-rewired test --env=jsdom",
    "eject": "react-scripts eject"
}

Тук заменихме оригиналните скриптове, които използват react-script за изпълнение и изграждане на нашето приложение, с react-app-rewired.

Сега можем да използваме незадължителния синтаксис за верижно свързване, за да изградим нашето приложение.

Първо, трябва да инсталираме някои пакети.

Стартирайте npm i axios bootstrap react-bootstrap formik yup react-router-dom, за да инсталирате HTTP клиента axios, React Bootstrap за стилизиране, Formik и Yup за изграждане на формуляри и добавяне на валидиране на формуляри и React Router за маршрутизиране на URL адреси към страниците, които създаваме.

Сега можем да напишем някакъв код. В App.js заместваме съществуващия код с:

import React from "react";
import { Router, Route } from "react-router-dom";
import HomePage from "./HomePage";
import AsteroidsSearchPage from "./AsteroidsSearchPage";
import { createBrowserHistory as createHistory } from "history";
import "./App.css";
import TopBar from "./TopBar";
const history = createHistory();
function App() {
  return (
    <div className="App">
      <Router history={history}>
        <TopBar />
        <Route path="/" exact component={HomePage} />
        <Route path="/search" exact component={AsteroidsSearchPage} />
      </Router>
    </div>
  );
}
export default App;

Така че да получим маршрутизиране от страна на клиента към нашите страници. В App.css заместваме съществуващия код с:

.center {
  text-align: center;
}

След това започваме да създаваме нови страници. Създайте файл с име AsteroidSearchPage.js в папката src и добавете:

import React, { useState, useEffect } from "react";
import { Formik } from "formik";
import Form from "react-bootstrap/Form";
import Col from "react-bootstrap/Col";
import Button from "react-bootstrap/Button";
import * as yup from "yup";
import Card from "react-bootstrap/Card";
import "./AsteroidsSearchPage.css";
import { searchFeed } from "./requests";
const schema = yup.object({
  startDate: yup
    .string()
    .required("Start date is required")
    .matches(
      /([12]\d{3}-(0[1-9]|1[0-2])-(0[1-9]|[12]\d|3[01]))/,
      "Invalid start date"
    ),
  endDate: yup
    .string()
    .required("End date is required")
    .matches(
      /([12]\d{3}-(0[1-9]|1[0-2])-(0[1-9]|[12]\d|3[01]))/,
      "Invalid end date"
    ),
});
function AsteroidsSearchPage() {
  const [feed, setFeed] = useState({});
  const [error, setError] = useState("");
const handleSubmit = async evt => {
    const isValid = await schema.validate(evt);
    if (!isValid) {
      return;
    }
    try {
      const response = await searchFeed(evt);
      setFeed(response.data.near_earth_objects);
    } catch (ex) {
      alert(ex?.response?.data?.error_message);
    }
  };
return (
    <div className="AsteroidsSearchPage">
      <h1 className="center">Search Asteroids</h1>
      <Formik validationSchema={schema} onSubmit={handleSubmit}>
        {({
          handleSubmit,
          handleChange,
          handleBlur,
          values,
          touched,
          isInvalid,
          errors,
        }) => (
          <Form noValidate onSubmit={handleSubmit}>
            <Form.Row>
              <Form.Group as={Col} md="12" controlId="startDate">
                <Form.Label>Start Date</Form.Label>
                <Form.Control
                  type="text"
                  name="startDate"
                  placeholder="YYYY-MM-DD"
                  value={values.startDate || ""}
                  onChange={handleChange}
                  isInvalid={touched.startDate && errors.startDate}
                />
                <Form.Control.Feedback type="invalid">
                  {errors.startDate}
                </Form.Control.Feedback>
              </Form.Group>
              <Form.Group as={Col} md="12" controlId="endDate">
                <Form.Label>End Date</Form.Label>
                <Form.Control
                  type="text"
                  name="endDate"
                  placeholder="YYYY-MM-DD"
                  value={values.endDate || ""}
                  onChange={handleChange}
                  isInvalid={touched.startDate && errors.endDate}
                />
<Form.Control.Feedback type="invalid">
                  {errors.endDate}
                </Form.Control.Feedback>
              </Form.Group>
            </Form.Row>
            <Button type="submit" style={{ marginRight: "10px" }}>
              Search
            </Button>
          </Form>
        )}
      </Formik>
      {Object.keys(feed)?.map(key => {
        return (
          <Card style={{ width: "90vw", margin: "0 auto" }}>
            <Card.Body>
              <Card.Title>{key}</Card.Title>
              {feed?.[key]?.length > 0
                ? feed?.[key]?.map(f => {
                    return (
                      <div style={{ paddingBottom: "10px" }}>
                        {f?.close_approach_data?.length > 0 ? (
                          <div>
                            <b>
                              Close Approach Date:
                              {f?.close_approach_data?.map(d => {
                                return <p>{d?.close_approach_date_full}</p>;
                              })}
                            </b>
                          </div>
                        ) : null}
                        <div>
                          Minimum Estimated Diameter:{" "}
                          {
                            f?.estimated_diameter?.kilometers
                              ?.estimated_diameter_min
                          }{" "}
                          km
                        </div>
                        <div>
                          Maximum Estimated Diameter:{" "}
                          {
                            f?.estimated_diameter?.kilometers
                              ?.estimated_diameter_max
                          }{" "}
                          km
                          <br />
                        </div>
                      </div>
                    );
                  })
                : null}
            </Card.Body>
          </Card>
        );
      })}
    </div>
  );
}
export default AsteroidsSearchPage;

Това е мястото, където добавяме формуляр за търсене на данни за астероиди от API на НАСА по период от време. Както полетата за начална, така и за крайна дата трябва да са във формат ГГГГ-ММ-ДД и променихме нашата проверка на формуляра, за да съответства на това в обекта schema.

След като проверката е направена, чрез извикване на функцията schema.validate, ние търсим. Отговорът има много вложени обекти, така че използваме незадължителния синтаксис за верижно свързване навсякъде в картите с резултати.

Преминаваме през масива close_approach_data и не приемаме, че винаги е дефиниран или има по-голяма от нула дължина, същото важи и за извикването на функцията map. Ние не приемаме, че функцията map винаги е дефинирана.

Ние също използваме незадължителния синтаксис за верижно свързване за f?.estimated_diameter?.kilometers
?.estimated_diameter_min
и f?.estimated_diameter?.kilometers
?.estimated_diameter_max
.

Колкото повече нива на влагане има, толкова по-малка е вероятността да обходите дървото на обектите успешно без незадължителния синтаксис за верижно свързване, тъй като има по-голям шанс вложените обекти да бъдат недефинирани.

Също така имайте предвид, че незадължителният синтаксис за верижно свързване работи за върнати резултати като Object.keys(feed)

В AsteroidSearchPage.css, който трябва да създадем в папката src, поставяме:

.AsteroidsSearchPage{
  margin: 0 auto;
  width: 90vw;
}

За да добавите малко полета към страницата.

След това изграждаме началната страница. Създайте файл с име HomePage.js в папката src и добавете:

import React, { useState, useEffect } from "react";
import { browse } from "./requests";
import Card from "react-bootstrap/Card";
import "./HomePage.css";
function HomePage() {
  const [initialized, setIntialized] = useState(false);
  const [feed, setFeed] = useState([]);
const browserFeed = async () => {
    const response = await browse();
    setFeed(response.data.near_earth_objects);
    setIntialized(true);
  };
useEffect(() => {
    if (!initialized) {
      browserFeed();
    }
  });
  return (
    <div className="HomePage">
      <h1 className='center'>Asteroids Close to Earth</h1>
      <br />
      {feed?.map(f => {
        return (
          <Card style={{ width: "90vw", margin: "0 auto" }}>
            <Card.Body>
              <Card.Title>{f?.name}</Card.Title>
              <div>
                {f?.close_approach_data?.length > 0 ? (
                  <div>
                    Close Approach Date:
                    {f?.close_approach_data?.map(d => {
                      return <p>{d?.close_approach_date_full}</p>;
                    })}
                  </div>
                ) : null}
                <p>
                  Minimum Estimated Diameter:{" "}
                  {f?.estimated_diameter?.kilometers?.estimated_diameter_min} km
                </p>
                <p>
                  Maximum Estimated Diameter:{" "}
                  {f?.estimated_diameter?.kilometers?.estimated_diameter_max} km
                </p>
              </div>
            </Card.Body>
          </Card>
        );
      })}
    </div>
  );
}
export default HomePage;

Тази страница е много подобна на компонента AsteroidSearchPage с използването на опционалния синтаксис за верижно свързване.

След това създайте HomePage.css в папката src и добавете:

.HomePage{
  text-align: left;
}

За да подравним нашия текст отляво.

След това създаваме requests.js в папката src и добавяме:

const APIURL = "https://api.nasa.gov/neo/rest/v1";
const axios = require("axios");
export const searchFeed = data =>
  axios.get(
    `${APIURL}/feed?start_date=${data.startDate}&end_date=${data.endDate}&api_key=${process.env.REACT_APP_APIKEY}`
  );
export const browse = () =>
  axios.get(`${APIURL}/neo/browse?api_key=${process.env.REACT_APP_APIKEY}`);

За да добавите функциите за правене на HTTP заявки към API на NASA за получаване на данни за астероиди и тяхното търсене.

process.env.REACT_APP_APIKEY има API ключа, когато добавите API ключа към .env файла на вашия проект с REACT_APP_APIKEY като ключ. Регистрирайте се за API ключ в НАСА.

Накрая добавяме TopBar.js към папката src и добавяме:

import React from "react";
import Navbar from "react-bootstrap/Navbar";
import Nav from "react-bootstrap/Nav";
import { withRouter } from "react-router-dom";
function TopBar({ location }) {
  const { pathname } = location;
  return (
    <Navbar bg="primary" expand="lg" variant="dark">
      <Navbar.Brand href="#home">NASA App</Navbar.Brand>
      <Navbar.Toggle aria-controls="basic-navbar-nav" />
      <Navbar.Collapse id="basic-navbar-nav">
        <Nav className="mr-auto">
          <Nav.Link href="/" active={pathname == "/"}>
            Home
          </Nav.Link>
          <Nav.Link href="/search" active={pathname.includes("/search")}>
            Search
          </Nav.Link>
        </Nav>
      </Navbar.Collapse>
    </Navbar>
  );
}
export default withRouter(TopBar);

Това е лентата за навигация в горната част на всяка страница в нашето приложение. Ние задаваме active prop, като проверяваме текущия URL адрес на страницата, за да получим акценти в нашите връзки.

След като това е направено, получаваме: