LEKCJA 18 - NEXTJS (p) - start

LEKCJA 18 - NEXTJS (p) - start
ukryj menu
SPEC
aktualizacja: 2022-02-11 09:17:48
0. film na YT

https://youtu.be/moSrrB1qDeQkopiuj

1. różnice w stosunku do react-a klienckiego

- React zajmuje się wyłącznie klientem, do pełnej aplikacji potrzeba warstwy serwerowej, autentykacji, routingu etc
- NextJS - używa Reacta do budowy komponentów


Najważniejsze możliwości NextJS

a) server side rendering

- zawartość strony może tworzyć się na serwerze, nie dopiero na kliencie
- nie jest to client - server w osobnych aplikacjach tylko jeden projekt zawierający nextjs i kod serwera
- jest wbudowany mechanizm do tworzenia REST API
- zoptymalizowane pod kątem silników wyszukiwarek, które widzą właściwą zawartość html zamiast js-a
- pobranie danych i render strony html odbywa się na serwerze


b) file based routig

- normalnie client react router zabrania faktycznego requesta do serwera przy zmianie adresu, a zamiast tego tworzy odpowiedni komponent - to jest SPA - Single Page Application
- w NextJS struktura routera jest zapewniana przez strukturę folderów i plików w katalogu /pages na serwerze, co jest zrozumiałe i działa intuicyjnie jak na zwykłych stronach html



c) full stack framework

- nextJS umożliwia używanie kodu backendowego w tej samej aplikacji czyli zamiast rest api i klienta, mamy jedną aplikację

d) składnia es6

- cała aplikacja umożliwia domyślnie pisanie kodu z użyciem składni import/export


2. start pracy

tworzymy aplikację w dowolnym folderze, chwilę to trwa:

npx create-next-app app001kopiuj

po zainstalowaniu można uruchomić domyślną aplikację na dev serwerze, która jest kompilowana do folderu .next

npm run devkopiuj

lub zbudować produkcyjną wersję i ją uruchomić

npm run build
npm startkopiuj



3. aplikacja - struktura plików

{appDir}
|
|__ pages // Miejsce w którym zaczyna się routing aplikacji
    |__ api // W Next.js możemy zrobić REST API i jego obsługa powinna znaleźć się w tym folderze
    |__ index.js // jest to strona widoczna pod adresem root "/"
|__ public // do przechowania danych statycznych jpg, favicon, js
|__ components // komponenty bez swojego rout-a wykorzstywane na stronach /pages
|__ styles // style
    |__ global.css // styl globalny dla całej aplikacji
    |__ <page/component>.module.css // styl dla określonej strony - musi zostać zachowana konwencja nazewnictwakopiuj




Nazewnictwo:
pages - z małej litery
Komponenty- z Dużej
Style - <komponent>.module.css


w tej chwili istotny jest folder /pages
zwróćmy uwagę że nie ma pliku index.html - odpowiednie podstrony renderowane są przez nextjs w locie, w odpowiedzi na requesty

dla jasności pracy, na początku usuwamy folder pages/api oraz styles/Home.module.css
oraz zawartość pages/index.js



4. index.js  - wejście do aplikacji

main page - route /

function Home() {
  return <h1>simple home page</h1>
}
export default Homekopiuj


nie potrzebujemy import react from "react"
do konstrukcji pages używamy zwykłych funkcji a nie strzałkowych
uruchom aplikację i zobacz w przeglądarce
skompilowana strona znajduje się w katalogu .next/server/pages/index.html


5. file based routing

do folderu pages dodajemy dwa pliki

index.js
kopiuj
login.jskopiuj

struktura aplikacji

{appDir}
|
|__ pages
    |__ index.js
    |__ login.jskopiuj



routing będzie taki:

/
/loginkopiuj




6. struktura projektu oparta na folderach

zamiast plików js można utworzyć strukturę folderów, co przydaje się w większej aplikacji i tak jest przejrzyściej

{appDir}
|
|__ pages
    |__ login
    |   |__ index.js
    |__ register
        |__ index.jskopiuj


routing

/login
/registerkopiuj


ogólnie podfoldery będą się zachowywać jak router

7. dynamic paths

zakładamy że strona
/news/index.js widoczna pod adresem /news
daje informację o wszystkich newsach

newsów może być wiele, ale strona jednego newsa powinna być jedna, więc
do określenia, że jest to dynamiczna ścieżka, nextJS potrzebuje nawiasów [] w nazwie pliku


/news/[newsid].jskopiuj

w takim razie
odczyt danych z adresu wygląda w tym pliku tak:


import { useRouter } from "next/router"

function DetailsPage() {

    const router = useRouter()
    console.log(router.query.newsid);
    return <h1> page in folder news - dynamic page {router.query.newsid}</h1>

}

export default DetailsPage;kopiuj




8. standardowe linki a href - wysył requesta do serwera

dodajmy plik pages/test/index.js

w nim


function TestPage() {

    return (
        <>
            <h1>page in /test folder</h1>

            <ul>
                <li><a href="/news/i-like-next-js">i like next.js - A HREF</a></li>
                <li><a href="/news/i-like-react-js">i like react.js - A HREF</a></li>
                <li><a href="/news/i-likeexpress-js">i like express.js - A HREF</a></li>
            </ul>
        </>
    )
}

export default TestPagekopiuj


w powyższym wypadku tracimy SPA (single page application) czyli mamy przeładowanie strony i przy każdej stronie nowy request do serwera, automatycznie tracimy też ewentualny state w komponentach

9. linki SPA

pozwalaja na lepszy user-experience
zachowują state między stronami
wyszukiwarki indeksują zawartość strony


Link: renderuje odpowiedni komponent oraz zmienia url w pasku adresu

podmień w pliku pages/test/index.js
i zaobserwuj działanie przeglądarki

import Link from "next/link"kopiuj

<li><Link href="/news/i-like-next-js">i like next.js - A HREF</Link></li>
<li><Link href="/news/i-like-react-js">i like react.js - A HREF</Link></li>
<li><Link href="/news/i-likeexpress-js">i like express.js - A HREF</Link></li>kopiuj


10. Head

wróćmy do pliku pages/index.js
wykorzystując wbudowany komponent Head (jest takich wbudowanych kilka, patrz dokumentacja https://nextjs.org/docs/api-reference/next/head)


import Head from 'next/head'
import styles from '../styles/Home.module.css'

function Home() {
  return (
    <div className={styles.container}>
      <Head>
        <title>Home Page</title>
        <meta name="description" content="Generated by create next app" />
        <link rel="icon" href="/favicon.ico" />
        <meta name='keywords' content='nextjs, head' />
      </Head>
      <h1 className="styles.title"> Home Page </h1>
    </div>
  )
}

export default Homekopiuj


plik /styles/Home.module.css

.container {
    margin: 0;
    padding: 0;
    background: blueviolet
}kopiuj


jak widać dla każdego route’a możemy zmienić zawartość sekcji head za pomocą
wbudowanego komponentu <Head>


<Head>
   <title>Home Page</title>
   <meta name="description" content="Generated by create next app" />
   <link rel="icon" href="/favicon.ico" />
   <meta name='keywords' content='nextjs, head' />
</Head>kopiuj




11. state

a) strona About używajaca state, wykonana za pomocą komponentu klasowego

plik pages/about.js

import Head from 'next/head'

import React from 'react'
import styles from '../styles/About.module.css'
class About extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      value: 'hello world'
    };
    console.log(props)
  }
  render() {
    return (
      <div className={styles.container}>
        <Head>
          <title> About Page </title>
        </Head>
        <h1> About </h1>
        <h2> {this.state.value} </h2>
      </div>
    );
  }
}
export default Aboutkopiuj


b) wykonaj to samo komponentem funkcyjnym


12. fetch

Pobieranie danych z REST API różni się od klasycznego Reacta.
Next wstrzykuje sobie te dane jako props z zewnętrznej funkcji.
Potrzebne będzie użycie wbudowanej funkcji getStaticProps() - jest to funkcja zwracająca obiekt z polem props
Taką funkcje należy umieścić pod kodem komponentu.


export const getStaticProps = async () => {
    const res = await fetch(`https://jsonplaceholder.typicode.com/posts?_limit=4`);
    const posts = await res.json();
    return {
        props: {
            posts
        }
    }
}kopiuj


cała aplikacja pobierająca posty

plik /posts.js


import Head from 'next/head'
import Item from "../components/Item"
import React from 'react'
import styles from '../styles/Posts.module.css'
class Posts extends React.Component {
    constructor(props) {
        super(props);
        console.log(props)
    }
    render() {
        return (
            <div>
                {
                    this.props.posts.map((post) => {
                        return <Item post={post} />
                    })
                }
            </div>
        )
    }
}
export default Postskopiuj


plik /components/item.js

import React, { Component } from 'react';

export default class Item extends Component {
    render() {
        console.log(this.props);
        return (
            <div>
                <h3>{this.props.post.id}</h3>
                <h4>{this.props.post.title}</h4>
                <h4>{this.props.post.body}</h4>
            </div>
        );
    }
}kopiuj


zadanie: wykonaj całość z użyciem komponentów funkcyjnych

13. REST API

NextJS posiada gotowy mechanizm wystawienia klasycznego REST API
Wszystkim co musimy zrobić jest:
W katalogu pages/api tworzymy folder o nazwie tego co będziemy wystawiać a
nim index.js i [id].js

poniżej krok po kroku utworzenie REST API i korzystającej z niego aplikacji

a) dane wstaw w pliku /pages/api/avatars/avatars.json

[
    {
        "id": "aatrox",
        "name": "Aatrox",
        "title": "the Darkin Blade",
        "icon": "http://ddragon.leagueoflegends.com/cdn/10.23.1/img/champion/Aatrox.png",
        "description": "Once honored defenders of Shurima against the Void, Aatrox and his brethren would eventually become an even greater threat to Runeterra, and were defeated only by cunning mortal sorcery. But after centuries of imprisonment, Aatrox was the first to find..."
    },
    {
        "id": "ahri",
        "name": "Ahri",
        "title": "the Nine-Tailed Fox",
        "icon": "http://ddragon.leagueoflegends.com/cdn/10.23.1/img/champion/Ahri.png",
        "description": "Innately connected to the latent power of Runeterra, Ahri is a vastaya who can reshape magic into orbs of raw energy. She revels in toying with her prey by manipulating their emotions before devouring their life essence. Despite her predatory nature..."
    },
    {
        "id": "akali",
        "name": "Akali",
        "title": "the Rogue Assassin",
        "icon": "http://ddragon.leagueoflegends.com/cdn/10.23.1/img/champion/Akali.png",
        "description": "Abandoning the Kinkou Order and her title of the Fist of Shadow, Akali now strikes alone, ready to be the deadly weapon her people need. Though she holds onto all she learned from her master Shen, she has pledged to defend Ionia from its enemies, one..."
    },
    {
        "id": "alistar",
        "name": "Alistar",
        "title": "the Minotaur",
        "icon": "http://ddragon.leagueoflegends.com/cdn/10.23.1/img/champion/Alistar.png",
        "description": "Always a mighty warrior with a fearsome reputation, Alistar seeks revenge for the death of his clan at the hands of the Noxian empire. Though he was enslaved and forced into the life of a gladiator, his unbreakable will was what kept him from truly..."
    },
    {
        "id": "amumu",
        "name": "Amumu",
        "title": "the Sad Mummy",
        "icon": "http://ddragon.leagueoflegends.com/cdn/10.23.1/img/champion/Amumu.png",
        "description": "Legend claims that Amumu is a lonely and melancholy soul from ancient Shurima, roaming the world in search of a friend. Doomed by an ancient curse to remain alone forever, his touch is death, his affection ruin. Those who claim to have seen him describe..."
    }
]
kopiuj

b) plik /pages/api/avatars/index.js

import avatars from "./avatars.json"
console.log(avatars);

export default function (req, res) {
    res.status(200).json(avatars)
}kopiuj


route

http://localhost:3000/api/avatarskopiuj

wyszukanie jednego avatara:


c) plik /pages/api/avatars/[id].js

import avatars from "./avatars.json"

export default function (req, res) {
    let id = req.query.id;
    const result = avatars.filter(av => av.id == id)
    if (result.length > 0)
        res.status(200).json(result[0])
    else
        res.status(404).json({ message: 'brak avatara o takim id' })
}kopiuj


adres

http://localhost:3000/api/avatars/aatroxkopiuj

d) użycie tych danych w aplikacji poza folderem api

plik /pages/av/index.js



import React from 'react'

import Avatar from "../../components/Avatar"

class Av extends React.Component {
    constructor(props) {
        super(props);
        console.log(props)
    }
    render() {
        return (
            <div>
                {
                    this.props.avs.map((data) => {
                        return <Avatar data={data} />
                    })
                }
            </div>
        )
    }
}
export default Av


export const getStaticProps = async () => {
    const res = await fetch(`http://localhost:3000/api/avatars`);
    const avs = await res.json();
    return {
        props: {
            avs
        }
    }
}
kopiuj

plik /components/Avatar.js

import React, { Component } from 'react';
import styles from '../styles/Avatar.module.css'

export default class Avatar extends Component {
    render() {
        console.log(styles.av);
        return (
            <div className={styles.av}>
                <h1>{this.props.data.id}</h1>
                <h4>{this.props.data.name}</h4>
                <h4>{this.props.data.title}</h4>
                <img src={this.props.data.icon} />
            </div >
        );
    }
}kopiuj


zadanie: wykonaj całość z użyciem komponentów funkcyjnych

14. pobieranie danych - różne sposoby

warto poczytać

https://nextjs.org/docs/basic-features/pages#static-generation-recommended
https://nextjs.org/docs/basic-features/data-fetching