Skip to main content

· 3 min read

react

Với bất kỳ ứng dụng nào việc hiển thị thông báo lỗi là không thể thiếu được. Các bạn có thể có rất nhiều cách làm để hiển thị được các thông báo lỗi này. Trong bài viết này tôi xin chia sẻ cách làm của tôi để các bạn cùng tham khảo nhé.

Mục đích của tôi khi viết component này phải đảm bảo nó dễ dùng như hàm window.alert() vậy. Trong bài viết này tôi sẽ sử dụng Material UI để hiển thị thông báo lỗi.

Toast Context

Vì chúng ta cần hàm hiển thị thông báo lỗi có thể sử dụng như window.alert() nên chúng ta cần khai báo một nơi lưu trữ để từ đó chúng ta có thể gọi được ở bất kỳ đâu trong ứng dụng. Có nhiều bạn sẽ nghĩ tới Redux nhưng trong bài viết này tôi sẽ sử dụng React Context API để làm việc đó. Chúng ta bắt đầu bằng việc khai báo ToastContext nào.

src/contexts/toast.js

import React, { useContext } from "react";

export const ToastContext = React.createContext();

export const useToast = () => useContext(ToastContext);

Toast Provider

Trong React Context API có định nghĩa Provider để khi có sử thay đổi trong component thì các component bên trong có thể nhận biết sử thay đổi đó và phản anh kết quả thay đổi lên màn hình. Ở đây chúng ta cần hiển thị message lỗi mỗi khi có component nào đó thông báo về việc hiển thị message lỗi.

import React, { useState } from "react";
import { Snackbar } from "@material-ui/core";
import { Alert } from "@material-ui/lab";
import { ToastContext } from "../contexts/toast";

export const ToastProvider = (props) => {
const { children } = props;
const [state, setState] = useState({ isOpen: false });

const show = (message) => {
setState({ isOpen: true, message });
};

const hide = () => setState({ isOpen: false });

const error = (message) => {
show({ type: "error", text: message });
};

const warn = (message) => {
show({ type: "warning", text: message });
};

const info = (message) => {
show({ type: "info", text: message });
};

const success = (message) => {
show({ type: "success", text: message });
};
const { isOpen, message } = state;
return (
<ToastContext.Provider
value={{
error: error,
warn: warn,
info: info,
success: success,
hide: hide,
}}
>
{children}
{message && (
<Snackbar open={isOpen} autoHideDuration={6000} onClose={hide}>
<Alert
elevation={6}
variant="filled"
onClose={hide}
severity={message.type}
>
{message.text}
</Alert>
</Snackbar>
)}
</ToastContext.Provider>
);
};

Sử dụng các hàm error, warn, info, success để hiển thị thông báo

Để sử dụng được các hàm này trong ứng dụng bạn cần khai báo nó trong component chính của ứng dụng

import React from "react";
import { ToastProvider } from "./providers/ToastProvider";
import { useToast } from "./contexts/toast";
import "./App.css";

function ButtonList() {
const { error, warn, info, success } = useToast();
return (
<>
<button onClick={() => error("error message!")}>error</button>
<button onClick={() => warn("warn message!")}>warn</button>
<button onClick={() => info("info message!")}>info</button>
<button onClick={() => success("success message!")}>success</button>
</>
);
}

function App() {
return (
<ToastProvider>
<ButtonList />
</ToastProvider>
);
}
export default App;

Cám ơn các bạn đã theo dõi bài viết. Hy vọng bài viết đã cung cấp cho các bạn thêm một các để hiển thị thông báo tốt hơn cho ứng dụng của bạn.

· 5 min read

container

Bạn có gặp khó khăn trong việc xây dựng môi trường phát triển ứng dụng không? Mỗi khi bắt đầu một dự án mới, việc cài đặt môi trường phát triển thường tốn khá nhiều thời gian. Do đó việc xây dựng và chia sẻ môi trường phát triển giữa các thành viên trong dự án là thực sự cần thiết. Trong bài viết này tôi sẽ chia sẻ với các bạn cách mà tôi đã làm với các dự án của mình.

docker-compose up -d

Tại sao lại sử dụng docker?

Chia sẻ qua một chút về quá trình trước khi tôi sử dụng docker trong các dự án của mình. Năm 2014, tôi bắt đầu xây dựng môi trường phát triển cho dự án sử dụng vargrant. Với vargrant tôi đã có thể đóng gói các dịch vụ được sử dụng trong dự án và chia sẻ với các thành viên trong dự án. Nhưng bạn biết không dự án có sử dụng PostgreSQL và Couchbase, khi đó tôi đã tạo 2 máy ảo vagrant cho các dịch vụ này. Bạn biết đấy, vargrant sẽ tạo ra một máy ảo hoàn chỉnh và sau đó tôi cài các dịch vụ tôi cần sử dụng lên đó. Nó thực sự là vấn đề với chiếc PC của tôi :). Ngoài ra bạn sẽ gặp khó khăn trong việc kết nối các máy ảo này với nhau nữa, bạn cần setting sao cho chúng ở cùng một mạng riêng.

Khi sử dụng docker thì sao? Với mỗi dịch vụ bạn tạo ra một container riêng giống như một máy ảo vargrant tôi đã tạo ở trên. Nhưng điểm khác biệt là gì?

  • Với docker bạn không cần lo lắng về vấn đề cài dịch vụ nữa. Nó đã tự động làm việc đó rồi.

  • docker không tạo ra một máy ảo hoàn chỉnh với các dịch vụ thừa trong đó. Nó đơn giản tạo ra một môi trường đủ để bạn chạy dịch vụ của mình.

  • Việc cấu hình mạng riêng và chuyển tiếp cổng vào container cũng được được thiết lập dễ dàng hơn.

Với chừng đó lý do là đủ để tôi chuyến sang sử dụng docker rồi :)

Tôi đã quản lý container bằng docker như thế nào?

Để quản lý container tôi sử dụng Docker Compose, công cụ này có sẵn khi bạn cài Docker Desktop

Cấu hình container

  • Sử dụng docker-compose.yml để cấu hình các dịch vụ bạn muốn sử dụng trong ứng dụng

Trong phạm vi bài viết này tôi sẽ cấu hình để tạo ra hai container cho các dịch vụ MySQL và DynamoDB.

docker-compose.yml

version: "3"
services:
mysql:
image: mysql:5.7.26
# set hostname để bạn có thể access vào container bằng tên này
container_name: mysql
ports:
# Cấu hình forward port từ host vào docker container
- "3306:3306"
volumes:
# Cấu hình thư mục chưa schema bạn muốn import vào MySQL
- ./mysql/initdb.d:/docker-entrypoint-initdb.d
# mount thư mục MySQL data để có thể backup dữ liệu nếu cần
- ./mysql/data:/var/lib/mysql
# Các cấu hình bạn cần thay đổi cho dịch vụ MySQL
- ./mysql/conf.d/my.cnf:/etc/mysql/my.cnf
# mount thư mục log để trace lỗi nếu cần
- ./mysql/log:/var/log/mysql
# các biến môi trường sử dụng qua tham số -e khi run container hoặc cấu hình trong .env như bên dưới
environment:
# set mật khẩu cho tài khoản root
- MYSQL_ROOT_PASSWORD=$DB_ROOT_PASSWORD
# tên database bạn muốn tạo sau khi container được khởi động
- MYSQL_DATABASE=$DB_DATABASE
# tạo thêm một user mới với tên được cấu hình trong $MYSQL_USER
- MYSQL_USER=$DB_USER
# set mật khẩu cho user được tạo ở trên
- MYSQL_PASSWORD=$DB_PASSWORD

dynamodb:
image: amazon/dynamodb-local
# set hostname để bạn có thể access vào container bằng tên này
container_name: dynamodb
ports:
# Cấu hình forward port từ host vào docker container
- "8000:8000"
volumes:
# mount thử mục data của DynamoDB để có thể backup
- ./dynamodb/data:/home/dynamodblocal/data
entrypoint: java
command: "-jar DynamoDBLocal.jar -sharedDb -dbPath /home/dynamodblocal/data"
  • Set các biến môi trường bằng .env

Tại thư mục chưa docker-compose.yml bạn tạo .env như sau:

.env

DB_ROOT_PASSWORD=hieunv@123456
DB_DATABASE=test
DB_USER=hieunv
DB_PASSWORD=hieunv@123456

Khởi động các dịch vụ

Bạn sử dụng docker-compose để khởi động các dịch vụ như đã cấu hình ở trên

docker-compose up -d

Xoá các container

Khi bạn không muốn sử dụng container nữa hoặc khi có lỗi container mà bạn muốn tạo lại thì có thể xoá nhanh các container đã tạo bằng lệnh sau:

docker-compose down -v

Cám ơn các bạn đã theo dõi bài viết. Hy vọng sau bài viết này các bạn sẽ sử dụng Docker trong dự án của mình.

· 6 min read

react

State Management là một trong những vấn đề tồn tại ở bất kỳ ứng dụng nào viết bằng React. Redux là một trong những công cụ như thế. Nó từng một thời là xu hướng được tất cả các ứng dụng viết bằng React sử dụng. Và mỗi khi nhắc đến React thì người ta cũng nhắc đến Redux luôn. Không những thế các biến thể của nó cũng được implement cho Angular như ngrx/store hay vuex cho VueJS. Tuy nhiên với ứng dụng ngày càng cồng kềnh thì việc định nghĩa và quản lý state ngày càng trở nên vất vả hơn. Trải qua một thời gian khá dài với Redux thì mãi tới gần đây Facebook mới cho ra đời Context API để giải quyết các vấn đề còn tồn tại với Redux. Trong bài viết này tôi sẽ đưa ra một số so sánh để giúp các bạn có thể chuyển từ ứng dụng viết bằng Redux sang sử dụng Context API.

Redux

Khi bạn muốn định nghĩa state với Redux thì mọi thứ có vẻ khá phức tạp. Bước đầu tiên bạn cần làm là phải khai báo đăng ký Redux Store với React

  • Khai báo Redux Store:

src/store/index.js

import { createStore, applyMiddleware, compose } from "redux";
import thunk from "redux-thunk";
import { createLogger } from "redux-logger";
// import reducer để thay đổi giá trị state
import createRootReducer from "../reducers";

// kiểm tra sự tồn tại của Redux Dev Tools và add nó vào phục vụ quá trình debug
const composeEnhancers =
typeof window === "object" && window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__
? window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__({})
: compose;

export default (initialState) => {
const { NODE_ENV } = process.env;

let store;
if (NODE_ENV === "development") {
// khởi tạo store sử dụng API của Redux, ở đây có sử dụng thêm redux-thunk để đơn giản hoá việc xử lý HTTP API
store = createStore(
createRootReducer(),
initialState,
composeEnhancers(applyMiddleware(thunk, createLogger()))
);

// cấu hình hot reload để mỗi khi bạn thay đổi *.js thì nó tự động được load lại
if (module.hot) {
module.hot.accept("../reducers", () => {
const nextRootReducer = require("../reducers").default;
store.replaceReducer(nextRootReducer);
});
}
} else {
// chỗ này để định nghĩa store trên môi trường production
store = createStore(
createRootReducer(),
initialState,
composeEnhancers(applyMiddleware(thunk))
);
}
return store;
};

Có vẻ khá nhiều việc cần phải làm với Redux. Nhưng vẫn chưa hết đâu :)

  • Đăng ký Redux Store vào ứng dụng React:

src/index.js

// sử dụng thư viện này để đăng ký Redux với React nhé
import { Provider } from "react-redux";
// import định nghĩa store được khai báo bên trên
import createStore from "./store";
//khởi tạo Redux store
const store = createStore({});

// render ứng dụng React
render(
// kết nối Redux store vào React
<Provider store={store}>
{
// mã nguồn ứng dụng được khái báo ở đây. Trong này bạn có thể truy cập vào state của Redux thông qua hàm connect
}
</Provider>,
document.getElementById("app-container")
);
  • Định nghĩa reducer để thay đổi giá trị state
const counterReducer = (state = { count: 0 }, action) => {
switch (action.type) {
case "INCREMENT":
return { count: count + 1 };
case "DECREMENT":
return { count: count - 1 };
default:
return state;
}
};
export default reducer;
  • Kết nỗi Redux Store với React Component
class Counter extends Component {
render() {
// Trạng thái và các hàm được đăng ký bởi hàm connect bên dưới
const { count, increment, decrement } = this.props;

return (
<div>
<button onClick={decrement}>-</button>
<span>{count}</span>
<button onClick={increment}>+</button>
</div>
);
}
}

export default connect(
// map giá trịg trong Redux Store vào props của component
(state) => ({ count: state.counter.count }),
// đăng ký các hàm sau vào props
(dispatch) => ({
// gọi action INCREMENT để tăng gía trị count.
increment: () => dispatch({ type: "INCREMENT" }),
// gọi action DECREMENT để tăng gía trị count
decrement: () => dispatch({ type: "DECREMENT" }),
})
)(Counter);

Có vẻ phải viết code khá nhiều và nhiều đoạn không tường minh nếu các bạn không nắm được Flux Architecture. Redux là một biến thể của Flux nên các bạn có thể tham khảo thêm Flux Architecture nhé. Bây giờ chúng ta thử với Context API thì sao nhé.

Context API

Với Context API bạn cũng cần một nơi để lưu trũ trạng thái của ứng dụng. Tuy nhiên Context API không định nghĩa tất cả vào trong một như Redux. Các bạn có thể quản lý các state khác nhau bằng các context khác nhau. Do đó với mỗi state, các bạn cần định nghĩa một context riêng cho nó.

  • Định nghĩa context

src/contexts/counter.js

import React, { useContext } from "react";

export const CounterContext = React.createContext();

export const useCounter = () => useContext(LoadingContext);
  • Định nghĩa provider để khai báo state và cung cấp các hàm thay đổi giá trị state
import React, { useState } from 'react';
import PropTypes from 'prop-types';
import { CounterContext } from '../../contexts/counter';

export function CounterProvider(props) {
const [count, setCount] = useState(0);

return (
<CounterContext.Provider
value={{
count: count,
decrement: () => setCount(count - 1)
increment: () => setCount(count + 1),
}}>
{props.children}
</CounterContext.Provider>
);
}

CounterContext.propTypes = {
children: PropTypes.node
};
  • Đăng ký CounterProvider trong ứng dụng React
// render ứng dụng React
render(
// kết nối Redux store vào React
<CounterProvider>
{
// sử dụng useCounter để lấy giá trị count và các hàm increment, decrement được provider truyển xuống trong giá trị value khai báo ở trên
}
</CounterProvider>,
document.getElementById("app-container")
);
  • Sử dụng useCounter trong React Component
export function Counter() {
const [count, increment, decrement] = useCounter();

return (
<div>
<button onClick={() => decrement()}>-</button>
{count}
<button onClick={() => increment()}>+</button>
</div>
);
}

Với React Context API mọi thứ trở nên đơn giản hơn bao giờ hết. Bạn vẫn còn dùng Redux chứ? Sau bài này bạn sẽ chuyển sang Context API chứ?

· 5 min read
Hieu Nguyen

react

Trong thế giới React thì chắc hẳn ai cũng biết đến Class Component và Function Component. Tuy nhiên có thể có những hiểu nhầm về hai loại component này. Trong bài viết này tôi sẽ thử so sánh hai cách viết này để giúp bạn có thể lựa chọn viết theo cách nào. Chúng ta cùng bắt đầu nhé.

Cú pháp

Khác nhau đầu tiên giữa Class ComponentFunction Component thể hiện ngay ở cách khai báo.

Class Component

import React, { Component } from "react";

class TestComponent extends Components {
// phương pháp này bắt buộc phải khai báo hàm để kết xuất mã HTML
render() {
return <div>TestComponent</div>;
}
}

Cách khai báo này khá quen thuộc với các bạn có nền tảng lập trình hướng đối tượng (OOP). Với những bạn mới học React hoặc chuyển sang học React thì phương pháp tiếp cận này có vẻ phù hợp và dễ hiểu.

Function Component

import React from "react";

export function TestComponent() {
// phương pháp xem kết xuất mã HTML như là giá trị trả về của hàm
return <div>TestComponent</div>;
}

Function component sử dụng cách tiếp cận khác đó là sử dụng pure function để khai báo component. Ban đầu function component được sử dụng để viết các component chỉ với mục đích kết xuất HTML mà thôi. Với các component với theo hướng tiếp cận này thì bạn sẽ không can thiệp được vào lifecycle của component. Do đó nó thướng được biết đến với tên gọi Stateless Component.

Props

Class Component

props trong Class Component được xem như giá trị truyển vào cho hàm khởi tạo class.

import React, { Component } from "react";

class TestComponent extends Components {
constructor(props) {
super(props); // bắt buộc phải có dòng này để gọi hàm khởi tạo của class cha nhé
}

render() {
return <div>TestComponent</div>;
}
}

Function Component

props trong Function Component thì được xem như là giá trị truyền vào hàm pure function khi định nghĩa component.

import React from "react";

export function TestComponent(props) {
return <div>TestComponent</div>;
}

Định nghĩa defaultPropspropTypes thì không có sự khác biệt giữa Class Component và Function Component.

TestComponent.defaultProps = {};

TestComponent.propTypes = {};

State

Trước khi React Hooks ra đời thì như đã nói ở trên Function Component con được biết đến với tên gọi Stateless Component. Nghĩa là nó không có state. Khi React Hooks ra đời thì Function Component cũng có state của riêng nó.

Class Component

state trong Class Component dược định nghĩa như sau:

import React, { Component } from "react";

class TestComponent extends Components {
constructor(props) {
super(props);
// khởi tạo giá trị state
this.state = { isLoading: false };
}

render() {
return <div>TestComponent</div>;
}
}

Khi muốn thay đổi giá trị state bạn gọi phương thức setState của component:

this.setState((state) => ({ isLoading: true }));

Function Component

state trong Function Component được định nghĩa như sau:

import React, { useState } from "react";

export function TestComponent(props) {
// giá trị khởi tạo state được truyền vào trong useState hook
const [state, setState] = useState({ isLoading: false });

return <div>TestComponent</div>;
}

Các bạn để ý hàm useState trả về giá trị của component state trong biến state và hàm setState. Khi muốn thay đổi giá trị của state thì bạn có thể gọi hàm setState.

setState({ isLoading: true });

Component Lifecycle

Với Class component các bạn sẽ thấy component lifecycle khá rõ ràng với các hàm như componentDidMount, componentDidUpdate. Function Component thì không như vậy, toàn bộ việc sử lý lifecycle đều thông qua useEffect hook.

// componentDidMount
useEffect(() => {
return () => {}; // componentWillUnmount
}, []);
// componentDidUpdate
useEffect(() => {
return () => {}; // componentWillUnmount
}, [state]);

Như các bạn thấy thì componentDidMountcomponentDidUpdate không chỉ định rõ khi nào thì hàm được gọi. Việc gọi hàm thì tự chúng ta hiểu dựa theo lifecycle của React component thôi. Với Function Component và useEffect thì khác, bạn có thể thấy [][state] chị định rõ ràng đối tượng phụ thuộc mà khi chúng thay đổi thì hàm truyển vào useEffect sẽ được gọi. Có vẻ như nó lại tường mình hơn là các hàm lifecycle trong class Component.

Sau sự có mặt của TypeScript thì có lẽ Class Component và React Hooks thì có lẽ bạn Class Component chiếm ưu thế tuyệt đối. Thời điểm đó anh em thi nhau viết Class Component với TypeScript và tôi cũng thế :). Ngay đến cả facebook cũng support TypeScript với create-react-app nữa cơ mà. Thế nhưng khi React Hooks xuất hiện thì có vẻ gió đã đổi chiều, việc xử lý state và lifecycle với hook có vẻ đơn giản hơn rất nhiều. Các bạn thì thấy thế nào. Bạn sẽ chọn cách nào với dự án của mình?

· One min read
Gao Wei

Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque elementum dignissim ultricies. Fusce rhoncus ipsum tempor eros aliquam consequat. Lorem ipsum dolor sit amet