Kinh nghiệm React
React là thư viện JavaScript phổ biến nhất để xây dựng giao diện người dùng (UI). Nó cho tốc độ phản hồi tuyệt vời khi user nhập liệu bằng cách sử dụng phương pháp mới để render trang web.
Contents
1. Lộ trình học React
1.1 Học React Class
- Cài đặt môi trường
Lưu ý: những chữ viết hoa. Mình từng viết React.Component chữ component mình không viết hoa và nó không chạy
//Để chạy dùng lệnh npm start
JSX khá giống với HTML
{1+1} // kết quả bằng 2 {/**/} // để ghi chú
3. Tìm hiểu về các components trong React
State chứa biến trong component, hàm map như vòng lặp duyệt qua mảng
import React from 'react'; class App extends React.Component { constructor() { super(); this.state = { data: [ { "id":1, "name":"Foo", "age":"20" }, { "id":2, "name":"Bar", "age":"30" }, { "id":3, "name":"Baz", "age":"40" } ] } } render() { return ( <div> <Header/> <table> <tbody> {this.state.data.map((person, i) => <TableRow key = {i} data = {person} />)} </tbody> </table> </div> ); } } class Header extends React.Component { render() { return ( <div> <h1>Header</h1> </div> ); } } class TableRow extends React.Component { render() { return ( <tr> <td>{this.props.data.id}</td> <td>{this.props.data.name}</td> <td>{this.props.data.age}</td> </tr> ); } } export default App;
4. State
5. Props
Props để truyền biến từ component này sang component khác
-
- setState => cập nhật state và khi state cập nhật sẽ render lại
- forUpdate => cập nhật và render lại
- findDOMNode => lấy element chỉnh sửa
import React from 'react'; import ReactDOM from 'react-dom'; class App extends React.Component { constructor() { super(); this.findDomNodeHandler = this.findDomNodeHandler.bind(this); }; findDomNodeHandler() { var myDiv = document.getElementById('myDiv'); ReactDOM.findDOMNode(myDiv).style.color = 'green'; } render() { return ( <div> <button onClick = {this.findDomNodeHandler}>FIND DOME NODE</button> <div id = "myDiv">NODE</div> </div> ); } } export default App;
Refs giống như id trong HTML để lấy element nào đó làm những việc nào đó.
import React from 'react'; import ReactDOM from 'react-dom'; class App extends React.Component { constructor(props) { super(props); this.state = { data: '' } this.updateState = this.updateState.bind(this); this.clearInput = this.clearInput.bind(this); }; updateState(e) { this.setState({data: e.target.value}); } clearInput() { this.setState({data: ''}); ReactDOM.findDOMNode(this.refs.myInput).focus(); } render() { return ( <div> <input value = {this.state.data} onChange = {this.updateState} ref = "myInput"></input> <button onClick = {this.clearInput}>CLEAR</button> <h4>{this.state.data}</h4> </div> ); } } export default App;
Key là định danh để chỉnh sửa element nhanh hơn
1.2 Học React Function sử dụng HOOK
- React Hook là gì và lợi ích
- Tìm hiểu các hook trong react
- useState trong React Hooks
import React, { useState } from 'react'; function Example() { // Khai báo một biến state mới, gọi nó là "count" const [count, setCount] = useState(0); return ( ); }
1.3 Material UI
Trang web material ui
2 thư viện design khác:
- Ant design: https://ant.design/
- Style Component: https://styled-components.com/docs/basic
import React, {useState, useEffect} from 'react'; import { makeStyles } from '@material-ui/core/styles'; import Table from '@material-ui/core/Table'; import TableBody from '@material-ui/core/TableBody'; import TableCell from '@material-ui/core/TableCell'; import TableContainer from '@material-ui/core/TableContainer'; import TableHead from '@material-ui/core/TableHead'; import TableRow from '@material-ui/core/TableRow'; import Paper from '@material-ui/core/Paper'; function createData(id, name, age) { return { id, name, age }; } export default function DenseTable() { const [rows, setRow] = useState([ createData(1, 'Foo', 10), createData(1, 'Bar', 20), createData(1, 'Baz', 30) ]); //const classes = useStyles(); const classes = { table: "minWidth: 650", w_300: { width: 300, }, w_50: { width: 50, } }; return ( <TableContainer component={Paper} style={classes.w_300}> <Table className={classes.table} size="small" aria-label="a dense table"> <TableHead> <TableRow> <TableCell style={classes.w_50}>id</TableCell> <TableCell align="left">Name</TableCell> <TableCell align="right">Age</TableCell> </TableRow> </TableHead> <TableBody> {rows.map((row) => ( <TableRow key={row.name}> <TableCell component="th" scope="row"> {row.id} </TableCell> <TableCell align="left">{row.name}</TableCell> <TableCell align="right">{row.age}</TableCell> </TableRow> ))} </TableBody> </Table> </TableContainer> ); }
1.4 Kết hợp Laravel với React
Đến đây mình khá bất ngờ, khi 2 công nghệ có thể kết hợp với nhau. Laravel làm backend và React làm frontend. Và mình biết được 2 công nghệ kết hợp với nhau thông qua API.
Đầu tiên các bạn cần tạo API bên Laravel theo bài viết này: https://code.tutsplus.com
Sau khi có API bên project React các bạn sử dụng fetch hoặc axios để lấy dữ liệu. https://reactjs.org/docs/faq-ajax.html
Lưu ý: khi bạn có dữ liệu và đổ lên state để sử dụng sẽ có một vòng lặp vô tận cứ chạy mãi, cứ gọi request phát sinh lỗi 429. Do đó bạn cần thêm tham số thứ 2 vào hàm useEffect() một mảng rỗng, để chỉ gọi request lần đầu tiên. Bạn tham khảo thêm bài viết https://topdev.vn
//code phía react import React, {useState, useEffect} from 'react'; import { makeStyles } from '@material-ui/core/styles'; import Table from '@material-ui/core/Table'; import TableBody from '@material-ui/core/TableBody'; import TableCell from '@material-ui/core/TableCell'; import TableContainer from '@material-ui/core/TableContainer'; import TableHead from '@material-ui/core/TableHead'; import TableRow from '@material-ui/core/TableRow'; import Paper from '@material-ui/core/Paper'; // function createData(id, name, age) { // return { id, name, age }; // } export default function DenseTable() { const [rows, setRow] = useState([]); useEffect( () => { fetch("http://localhost/two_cn/server/api/data") .then( function(response) { if (response.status !== 200) { console.log('Lỗi, mã lỗi ' + response.status); return; } // parse response data response.json().then(data => { //console.log(data); setRow(data); }) } ) .catch(err => { console.log('Error :-S', err) }); }, []); //const classes = useStyles(); const classes = { table: "minWidth: 650", w_300: { width: 300, }, w_50: { width: 50, } }; return ( <TableContainer component={Paper} style={classes.w_300}> <Table className={classes.table} size="small" aria-label="a dense table"> <TableHead> <TableRow> <TableCell style={classes.w_50}>id</TableCell> <TableCell align="left">Name</TableCell> <TableCell align="right">Age</TableCell> </TableRow> </TableHead> <TableBody> {rows.map((row) => ( <TableRow key={row.name}> <TableCell component="th" scope="row"> {row.id} </TableCell> <TableCell align="left">{row.name}</TableCell> <TableCell align="right">{row.age}</TableCell> </TableRow> ))} </TableBody> </Table> </TableContainer> ); }
1.5 Validate form bằng redux-form và kết hợp react với redux
Bạn cần đọc các bải viết này để hiểu rõ nó:
- Redux là gì? Hiểu rõ cơ bản cách dùng Redux
- Cơ bản về redux-form:
Bài viết này cho bạn cái nhìn đơn giản nhưng khá rõ về redux-form. Đồng thời có một ví dụ đơn giản theo từng quy trình tạo redux-form: Tạo Store => Tạo Form => Tạo Component <Field/> => Gọi form và xử lý submit - Bắt đầu với Redux? Tại sao Redux:
Bài viết này là cái nhìn tổng quan và chi tiết về redux. Bao gồm tạo store, action, reducer. Bên cạnh đó là code ví dụ cho từng phần. - Redux Form Validate Tutorial:
Đây là bài viết hướng dẫn tạo một redux-form hướng dẫn từ a-z. Bao gồm cả validate. Cũng khá là dễ hiểu và đơn giản. - Ví dụ đơn giản với react và redux tại phía client
Bài viết lại là một project làm bằng react và redux đơn giản. Nó là một quy trình của redux. Tạo các state dùng chung => Tạo reducer => tạo store với reducer => Truyền store => Sử dụng store tại component => component gọi action reducer
2. Kinh nghiệm
2.1 return
Return nhiều element chúng ta cần bọc nó với 1 container (div, …)
2.2. if else
Không thể sử dụng If Else trong JSX, thay vào đó sử dụng điều kiện rút gọn
import React from 'react'; class App extends React.Component { render() { var i = 1; return ( <div> <h1>{i == 1 ? 'True!' : 'False'}</h1> </div> ); } } export default App;
2.3 style
Bỏ vào một biến để sử dụng
import React from 'react'; class App extends React.Component { render() { var myStyle = { fontSize: 100, color: '#FF0000' } return ( <div> <h1 style = {myStyle}>Header</h1> </div> ); } } export default App;
2.4 Thực hiện hành động nào đó sau khi gọi API
const add_person = (person) => { console.log(person); axios.post('http://localhost/two_cn/server/api/data', person) .then(res => { console.log(res.data); }); } get_data();
Bạn đang bực bội mình viết gọi API rồi tới hàm mà sao nó cứ chạy hàm dưới trước rồi gọi API sau. Để giải quyết bạn phải sử dụng promise. Lúc này nó mới hiểu là gọi API xong mới xử lý.
const add_person = (person) => { console.log(person); axios.post('http://localhost/two_cn/server/api/data', person) .then(res => { console.log(res.data); get_data(); }); } // hàm get_data() sẽ thực hiện khi gọi API xong
Tham khảo thêm: Làm việc với asynchronous APIs
2.5 Dấu …
Ký tự gì lạ vậy trời. Đó là câu hỏi lúc đầu tôi mới bắt đầu học react. Thật ra nó chỉ là tách phần tử ra giúp mình thôi. (đúng hơn là copy mảng hoặc đối tượng)
Ví dụ bạn có mảng a = [5,6,7,8,9]
b = […a, 7] => b = [5,6,7,8,9,7]
Tương tự với object.
Bạn tham khảo thêm tại 2 bài viết này nhé!
2.6 Redux
Tạo defaultState
const defaultValue = { listUser: [], } const defaultState = defaultValue;
Tạo reducer
function reducerUser(state = defaultState, action) { switch (action.type) { case 'ADD': add_person(action.formData); return state; case 'SET_DATA': let users_data=[...action.data]; return {...state, listUser:users_data}; default: return state; } //return state; }
Đăng ký rootReducer
const rootReducer = combineReducers({ reducerUser, form: formReducer });
Tạo store
const store = createStore(rootReducer);
Tạo provider
ReactDOM.render( <Provider store={store}> <Router history={hist}> <Switch> <Route path="/admin" component={Admin} /> <Route path="/rtl" component={RTL} /> <Redirect from="/" to="/admin/dashboard" /> </Switch> </Router> </Provider>, document.getElementById("root") );
Sử dụng
function TableList = (props) { const {listUser} = props; } export default connect (function(state) { console.log("FROM VIEWER:", state); // in giá trị state return { listUser: state.reducerUser.listUser }; })(TableList);
Gọi action đến store
const add_person = (person) => { props.dispatch({ type: 'ADD', formData: person }); }
2.7 Redux-form
2.7.1 Quy trình
Tạo store cho redux-form
const rootReducer = combineReducers({ form: formReducer }); const store = createStore(rootReducer);
Tạo form, Field của redux-form
const renderField = (props) => { const { input, label, type, meta: { touched, error, warning } } = props; return ( <div> <label className="control-label">{label}</label> <div> <input {...input} placeholder={label} type={type} className="form-control"/> {touched && ((error && <span className="text-danger">{error}</span>) || (warning && <span>{warning}</span>))} </div> </div> ); } let Create = (props) => { /*--props--*/ const { handleSubmit, pristine, reset, submitting } = props; /*--Variable--*/ /*--state--*/ const dataForm = props.dataForm; /*--method--*/ const cancel = props.cancel; /**--style--*/ return( <div> <form onSubmit={ handleSubmit }> <div className="form-group"> <Field name="name" component={renderField} label="Name"/> </div> <div className="form-group"> <Field name="age" component={renderField} label="Age"/> </div> <br /> <br /> <div className="form-group"> <button type="submit" className="btn btn-primary">Submit</button> <Button style={{marginLeft: '10px'}} variant="contained" color="secondary" onClick={cancel}> Hủy </Button> </div> </form> </div> ); }
tạo redux-form:
- Tên form là duy nhất nhé
Create = reduxForm({ form: 'create', // a unique identifier for this form validate })(Create)
Validate
const validate = values => { const errors = {} if (!values.name) { errors.name = 'Không được để trống' } else if (values.name.length < 2) { errors.name = 'Nhập ít nhất 2 ký tự' } if (!values.age) { errors.age = 'Không được để trống' } return errors }
Gọi ra xài thôi
import React, {useState, useEffect, useCallback, updateState} from "react"; import ............ function LayoutCRUD(props) { const handleSubmitFormCreate = (person) => { ......... } return ( <Create onSubmit={handleSubmitFormCreate} /> ); }
(*) Create truyền props onSubmit là props mặc định của redux-form cho phương thức submit. Ở Form tạo của redux-form sẽ sử dụng props này với tên là handleSubmit. Bạn lưu ý kỹ chỗ này nhé.
2.7.2 Set giá trị cho form
Tình huống:
- Bạn nhấn nút thêm mới và muốn các trường trở về giá trị rỗng
- Bạn nhấn nút chỉnh sửa và muốn đổ giá trị đó vào cho form
Khi mới tìm hiểu, mình bị bí chỗ này, mất cả tuần tìm hiểu không ra. Cảm thấy khá nãn, nhưng cuối cùng cũng thành công. Bạn làm theo bên dưới nhé.
Cách 1:
Ở file gọi redux-form của bạn bạn sẽ truyền props data qua cho component chứa redux-form
import React, {useState, useEffect, useCallback, updateState} from "react"; import ............ function LayoutCRUD(props) { const [dataForm, setDataForm] = useState(personEmpty); const handleSubmitFormCreate = (person) => { ......... } return ( <Create onSubmit={handleSubmitFormCreate} dataForm={dataForm} /> ); }
component chứa redux-form, bạn nhận giá trị từ props này
let Create = (props) => { /*--props--*/ const { handleSubmit, pristine, reset, submitting } = props; /*--state--*/ const dataForm = props.dataForm; return( <div> <form onSubmit={ handleSubmit }> <Field name="name" component={renderField} label="Name"/> <Field name="age" component={renderField} label="Age"/> <button type="submit" className="btn btn-primary">Submit</button> </form> </div> ); }
Có data cho component rồi thì, set giá trị bằng cách viết vào function connect
- Gồm connect và hàm mapStateToProps
let Create = (props) => { /*--props--*/ const { handleSubmit, pristine, reset, submitting } = props; /*--state--*/ const dataForm = props.dataForm; return( <div> <form onSubmit={ handleSubmit }> <Field name="name" component={renderField} label="Name"/> <Field name="age" component={renderField} label="Age"/> <button type="submit" className="btn btn-primary">Submit</button> </form> </div> ); } const mapStateToProps = (state, props) => ({ initialValues: props.dataForm, // retrieve name from redux store }) export default connect( mapStateToProps )(reduxForm({ form: 'simple', // a unique identifier for this form enableReinitialize: true })(Create))
Cách 2:
- add props initialized, initialize, change
- Sử dụng trong useEffect nếu đã khỏi tạo => thay đổi giá trị ngược lại thì khởi tạo giá trị.
let TableCustom = (props) => { /*--props--*/ const { handleSubmit, pristine, reset, submitting } = props; const {initialized, initialize, change} = props; const editRow = props.editRow; useEffect( () => { if(initialized) { change("name", editRow.name); change("age", editRow.age); } else { initialize({...editRow}); } }, []); return ( ... ); }
Cách 3: (giống cách 2 nhưng chỉ sử dụng initialize)
useEffect( () => { initialize({...editRow}); }, []);
2.7.3 Sử dụng props của redux-form
Bạn sử dụng như ở mục 2.7.3 Cần xài cái nào thì bỏ vô như thế này:
const { handleSubmit, pristine, reset, submitting } = props; const {initialized, initialize, change} = props;
Trang chủ redux-form có các props mà redux-form cung cấp: https://redux-form.com/
2.7.4 Thẻ form và handleSubmit
Thẻ form phải:
- Bao cả Field và nút submit
<form onSubmit={ handleSubmit }> <Field name="name" component={renderField} label="Name"/> <Field name="age" component={renderField} label="Age"/> <button type="submit" className="btn btn-primary">Submit</button> </form>
Hoặc:
- Chỉ bao quanh Field
- button sẽ bắt sự kiện onClick và truyền vào props handleSubmit của redux-form
<form> <Field name="name" component={renderField} label="Name"/> <Field name="age" component={renderField} label="Age"/> </form> <button type="submit" className="btn btn-primary" onSubmit={handleSubmit}>Submit</button>
2.8 Việc sử dụng, copy mảng, đối tượng
Bạn nên sử dụng kí tự “…” để truyền mảng hoặc đối tượng vào (copy thành mảng, đối tượng khác), để tránh phát sinh lỗi.
initialize({...editRow}); //thay vì: initialize(editRow);
2.9 Lỗi thiếu thư viện
Kiểm tra file package.json có thư viện đó chưa? Nếu chưa:
- tiến hành install
- cài xong => npm start lại
2.10 Plugin cho react
2.10.1 Cách tìm kiếm google
Bạn search như sau:
- vấn đề cần tìm + npm
Hoặc
- vấn đề cần tìm + react
Tiêu chí chọn plugin:
- Dễ cài đặt
- Nhiều option để dễ tùy biến
- Lượt tải nhiều
2.10.2 Các plugin react tổng hợp
- Loading Data: react-spinner
- Đọc HTML: react-html-parser (code html => render ra , thường là CKEditor lưu dạng HTML)
- Editor:
- Tag tab:
3. IDE Webstome
3.1 Đi đến nhanh một component con
ctrl + click
3.2 Phát hiện các biến không sử dụng
Sử dụng plugin … cho webstome IDE.
4. Theme Material
4.1 Material Dashboard React
Link trang chủ theme: https://www.creative-tim.com
Bạn vào Views/Layout/Admin.js
Tìm state FixedClass và bỏ chữ “show” đi.
Nếu muốn bỏ luôn bánh răng:
Bỏ luôn component <FixedPlugin> ở gần cuối file
5. Deploy lên server
Bạn chạy lệnh:
npm run build
Sau khi chạy xong,bạn vào thư mục public sẽ có 2 file dạng …app.js và …app.css
Bạn upload 2 file đó lên server là có thể chạy được.
Trường hợp có hình ảnh:
- public/images: tất cả hình ảnh sẽ nằm trong đây
- Bạn cũng upload folder này lên luôn nha.