Facebook Pixel

Netflix Clone với ReactJS, Styled Components và Firebase (Firestore & Auth) - Phần 2

01 Dec, 2021

Sau phần 1 của series này, chắc hẵn các bạn đã nóng lòng muốn bắt tay ngay vào code rồi đúng không 😁. Okie, trong phần thứ 2 của series này, chúng ta sẽ cùng nhau tạo phần Story của trang Home dành cho khách trên trang Netflix nhé (Trang lúc ta vào mà chưa login ấy 😁). Nào! bắt đâu thôi!

Netflix Clone với ReactJS, Styled Components và Firebase (Firestore & Auth) - Phần 2

Mục Lục

Sau phần 1 của series này, chắc hẵn các bạn đã nóng lòng muốn bắt tay ngay vào code rồi đúng không 😁. Okie, trong phần thứ 2 của series này, chúng ta sẽ cùng nhau tạo phần Story của trang Home dành cho khách trên trang Netflix nhé (Trang lúc ta vào mà chưa login ấy 😁). Nào! bắt đâu thôi!

I. Tạo file data tĩnh.

Tạo file data tĩnh là sao? Những data tĩnh là những data như hình bên dưới, chúng ta sẽ không lưu trực tiếp trên file .js hay .jsx mà chúng ta lưu chúng dưới dạng JSON và load lên thôi.

Static data in UI

Trong project của chúng ta, các bạn tạo thư mục fixtures, trong đó ta sẽ tạo 2 file data khác nhau, faqs.json cho phần FAQsstories.json cho các Story của trang, như hình bên dưới.

fixtures folder

Trong các file đã tạo ở trên, các bạn thêm data cho nó như sau:

  • faqs.json
JSON
[
	{
		"id": "dffee64b-bf69-4f7f-a056-e4e99718c2d7",
		"header": "What is Netflix?",
		"body": "Netflix is a streaming service that offers a wide variety of award-winning TV shows, movies, anime, documentaries, and more on thousands of internet-connected devices.\n\nYou can watch as much as you want, whenever you want without a single commercial – all for one low monthly price. There's always something new to discover and new TV shows and movies are added every week!"
	},
	{
		"id": "308e3980-d9b8-43f6-80a5-2a855bc7364e",
		"header": "How much does Netflix cost?",
		"body": "Watch Netflix on your smartphone, tablet, Smart TV, laptop, or streaming device, all for one fixed monthly fee. Plans range from 70,000 ₫ to 260,000 ₫ a month. No extra costs, no contracts."
	},
	{
		"id": "a8231110-c2a7-42a3-998a-ec936e8e500d",
		"header": "Where can I watch?",
		"body": "Watch anywhere, anytime. Sign in with your Netflix account to watch instantly on the web at netflix.com from your personal computer or on any internet-connected device that offers the Netflix app, including smart TVs, smartphones, tablets, streaming media players and game consoles.\n\nYou can also download your favorite shows with the iOS, Android, or Windows 10 app. Use downloads to watch while you're on the go and without an internet connection. Take Netflix with you anywhere."
	},
	{
		"id": "d40d6041-4573-4794-8c60-41afa8aaec3b",
		"header": "How do I cancel?",
		"body": "Netflix is flexible. There are no pesky contracts and no commitments. You can easily cancel your account online in two clicks. There are no cancellation fees – start or stop your account anytime."
	},
	{
		"id": "e32a5639-6149-474c-8dcc-3231acc18484",
		"header": "What can I watch on Netflix?",
		"body": "Netflix has an extensive library of feature films, documentaries, TV shows, anime, award-winning Netflix originals, and more. Watch as much as you want, anytime you want."
	},
	{
		"id": "ee556aad-708a-4f6e-8ba9-32921ee885d3",
		"header": "Is Netflix good for kids?",
		"body": "The Netflix Kids experience is included in your membership to give parents control while kids enjoy family-friendly TV shows and movies in their own space.\n\nKids profiles come with PIN-protected parental controls that let you restrict the maturity rating of content kids can watch and block specific titles you don’t want kids to see."
	}
]
faqs.json
  • stories.json
JSON
[
    {
        "id": "eeba294b-0629-4360-94a4-c085ef78b33d",
        "title": "Enjoy on your TV.",
        "subTitle": "Watch on Smart TVs, Playstation, Xbox, Chromecast, Apple TV, Blu-ray players, and more.",
        "image": "/images/misc/home-tv.png",
        "alt": "Tiger King on Netflix",
        "direction": "row"
    },
    {
        "id": "071ddd8a-2ba3-4e7d-bf48-b1c1c2c0013a",
        "title": "Download your shows to watch offline.",
        "subTitle": "Save your favorites easily and always have something to watch.",
        "image": "/images/misc/home-mobile.png",
        "alt": "Watch on mobile",
        "direction": "row-reverse"
    },
    {
        "id": "b5b21681-662a-44b8-83e6-63c7e3cf567a",
        "title": "Watch everywhere.",
        "subTitle": "Stream unlimited movies and TV shows on your phone, tablet, laptop, and TV.",
        "alt": "Money Heist on Netflix",
        "direction": "row"
    },
    {
        "id": "d3fdfefb-3d43-4422-84b7-257a72a624b3",
        "title": "Create profiles for kids.",
        "subTitle": "Send kids on adventures with their favorite characters in a space made just for them—free with your membership.",
        "image": "/images/misc/for-kids.png",
        "alt": "Netflix for kids",
        "direction": "row-reverse"
    }
]
stories.json
Note: để có được id như mình thì các bạn lên trang Online GUID / UUID Generator tại đây để có thể lấy các id tự generate ra nhé.

Tiếp theo các bạn tạo thư mục images/misc để chứa hình ảnh cho phần Story Guest Home Page của chúng ta. Hình ảnh các bạn có thể download tại đây.

Bạn cũng có thể đặt tên thư mục tùy ý nhưng phải đảm bảo là sau đó bạn phải edit lại phần data JSON của file stories.json cho đúng path để ảnh có thể load được nhé 😉.

II. Tạo component Story.

Component Story là sao? Thì component Story là những component nó view những content như hình bên dưới.

Story component

Đầu tiên ta cần tạo folder components, trong folder components ta tạo folder và file như sau:

Story component folder

Note:

  • story.jsx: file chứa các child component được define để build nên component story.
  • index.jsx: nó là một compound component, nó dùng để chứa các component được định nghĩa ở file story.jsx và sử dụng nó để build nên component story đồng thời quản lý các state hoặc props được truyền vào.

Khái quát một chút về compound component:

Compound components là 1 design pattern trong React, nó là loại component có thể quản lí internal state của nó và component đó được render như thế nào thì đều nằm ở phía implementation chứ không phải declaration .

Okie quay trở lại, trong folder src ta tạo file global-styles.jsx để css global cho cả trang của chúng ta, ta thêm code vào như bên dưới.

JS
import { createGlobalStyle } from 'styled-components';

export const GlobalStyles = createGlobalStyle`
    html, body {
        font-family: Arial, Helvetica, sans-serif;
        -webkit-font-smoothing: antialiased;
        -moz-osx-font-smoothing: grayscale;
        background-color: #000;
        color: #333;
        font-size: 16px;
    }
`;
global-styles.jsx

Sau đó sử dụng component này ở trong /src/index.js như bên dưới:

JS
import React from "react";
import ReactDOM from "react-dom";
import "./index.css";
import App from "./App";
import reportWebVitals from "./reportWebVitals";
import { GlobalStyles } from "./global-styles";

ReactDOM.render(
	<React.StrictMode>
		<GlobalStyles />
    	<App />
  </React.StrictMode>,
  document.getElementById("root")
);

// If you want to start measuring performance in your app, pass a function
// to log results (for example: reportWebVitals(console.log))
// or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals
reportWebVitals(); 
index.js

Trong file story.jsx ta define các child component như sau:

JS
import styled from "styled-components/macro";

export const Inner = styled.div`
	display: flex;
	align-items: center;
	flex-direction: ${({ direction }) => direction};
	justify-content: space-between;
	max-width: 1100px;
	margin: auto;
	width: 100%;

	@media screen and (max-width: 1000px) {
		flex-direction: column;
	}
`;

export const BlockPane = styled.div`
	width: 50%;

	@media screen and (max-width: 1000px){
		width: 100%;
		padding: 0 45px;
		text-align: center;
	}
`;

export const Title = styled.h1`
	font-size: 50px;
	line-height: 1.1;
	margin-bottom: 8px;

	@media screen and (max-width: 600px){
		font-size: 35px;
	}
`;

export const SubTitle = styled.h2`
	font-size: 26px;
	line-height: normal;
	font-weight: normal;

	@media screen and (max-width: 600px){
		font-size: 18px;
	}
`;

export const Image = styled.img`
	max-width: 100%;
	width: 507px;
	height: auto;
`;

export const Item = styled.div`
	display: flex;
	border-bottom: 8px solid #222;
	padding: 50px 5%;
	color: #fff;
	overflow: hidden;
`;

export const Container = styled.div`
	@media (max-width: 1000px) {
		${Item}:last-of-type h2 {
			margin-bottom: 50px;
		}
	}
`;
story.jsx

Tiếp theo đến với file /story/index.jsx, vì nó là một compound component, nên tại đây ta có thể quản lý và sử dụng các props được binding vào như sau:

JS
import React from "react";
import { BlockPane, Container, Image, Inner, Item, SubTitle, Title } from "./styles/story";

export default function Story({ children, direction = 'row', ...restProps }) {
    return (
        <Item {...restProps}>
            <Inner direction={direction}>{children}</Inner>
        </Item>
    );
}

Story.Container = function StoryContainer({ children, ...restProps }) {
    return <Container {...restProps}>{children}</Container>
}

Story.BlockPane = function StoryBlockPane({ children, ...restProps }) {
    return <BlockPane {...restProps}>{children}</BlockPane>
}

Story.Title = function StoryTitle({ children, ...restProps }) {
    return <Title {...restProps}>{children}</Title>
}

Story.SubTitle = function StorySubTitle({ children, ...restProps }) {
    return <SubTitle {...restProps}>{children}</SubTitle>
}

Story.Image = function StoryImage({ children, ...restProps }) {
    return <Image {...restProps}>{children}</Image>
}

Story.SubTitle = function StorySubTitle({ children, ...restProps }) {
    return <SubTitle {...restProps}>{children}</SubTitle>
}
index.jsx

Note:

  • children: prop chứa các content của component,
  • direction: prop này dùng để nhận các value css của component để sử dụng trong thằng <Inner /> của file story.jsx,
  • restProps: là một rest parameter.

Okie! define như vậy là đủ rồi hehe 😁, Vậy sử dụng nó như thế nào? Thì để sử dụng những thứ mà chúng ta define nảy giờ như sau:

  • Trong file App.js
JS
import React from 'react';
import Story from './components/story/index';
import storiesData from './fixtures/stories.json'

export default function App() {
	return (
		<Story.Container>
			{storiesData.map(story => (
				<Story key={story.id} direction={story.direction}>
					<Story.BlockPane>
						<Story.Title>{story.title}</Story.Title>
						<Story.SubTitle>{story.subTitle}</Story.SubTitle>
					</Story.BlockPane>
					{story.image ? (
						<Story.BlockPane>
							<Story.Image src={story.image} alt={story.alt}></Story.Image>
						</Story.BlockPane>
					) : ''}
				</Story>
			))}
		</Story.Container>
	);
}
App.js

Đấy cũng đơn giản phết nhỉ 😁, các component về sau chúng ta cũng tạo theo cách này nhé 😉, run thử xem chạy như thế nào nhé.

Story component in UI

Okie! work tốt nhá hehe 😎.

III. Tổng kết.

Okie! Phần thứ 2 của series này tới đây thôi, cơ bản chúng ta cũng làm được 50% Guest Home Page rồi. Đồng thời với ngần này thì đã đủ cho các bạn làm quen và code được theo style của styled-component rồi. Trong phần tiếp theo chúng ta sẽ làm phần FAQs của Guest Home Page này và install thêm các tool như ESLintPrettier để có thể check convention của chúng ta khi coding giúp code của chúng ta gọn gàng, đẹp và bớt "gà" hơn nhé 😁.

Bài viết liên quan

Lập trình backend expressjs

xây dựng hệ thống microservices
  • Kiến trúc Hexagonal và ứng dụngal font-
  • TypeScript: OOP và nguyên lý SOLIDal font-
  • Event-Driven Architecture, Queue & PubSubal font-
  • Basic scalable System Designal font-

Đăng ký nhận thông báo

Đừng bỏ lỡ những bài viết thú vị từ 200Lab