React 包裝 Model Class
tags: Web
React
category: Front-End
description: React 包裝 Model Class 類別
created_at: 2021/07/07 23:00:00
目的
- 把
Model
抽離,統一格式 - 處理表單的時候少寫一點
useState
事前準備
- 裝好 Node.js
先建立你的 React Project
$ npx create-react-app model-app
會使用一個簡單的表單當作範例
整理目錄
<src>
<app>
<components>
List.js
MyForm.js
App.js
<models>
ModelBase.js
User.js
index.js
先不管 models/*
裡面放了什麼,先用傳統的做法做一次
// file: App.js
import {useState} from "react";
import MyForm from "./components/MyForm";
import List from "./components/List";
function App() {
const [users, setUsers] = useState([]);
const pushUser = user => {
const newUsers = [...users];
newUsers.push(user);
setUsers(newUsers);
}
return (
<div>
<MyForm pushUser={pushUser}/>
<hr/>
<List users={users}/>
</div>
)
}
export default App;
// file: MyForm.js
import {useState} from "react";
function MyForm(props) {
const [email, setEmail] = useState('')
const [name, setName] = useState('')
const [username, setUsername] = useState('')
const [password, setPassword] = useState('')
return (
<form>
<div>
<label>
Email:
<input type="text" onChange={e => setEmail(e.target.value)} defaultValue={email}/>
</label>
</div>
<div>
<label>
Name:
<input type="text" onChange={e => setName(e.target.value)} defaultValue={name}/>
</label>
</div>
<div>
<label>
Username:
<input type="text" onChange={e => setUsername(e.target.value)} defaultValue={username}/>
</label>
</div>
<div>
<label>
Password:
<input type="password" onChange={e => setPassword(e.target.value)} defaultValue={password}/>
</label>
</div>
<div>
<button type="button" onClick={() => props.pushUser({email, name, username, password})}>Submit</button>
</div>
</form>
)
}
export default MyForm;
// file: List.js
function List(props) {
const {users} = props;
return (
<div>
List:
{users.length > 0 ? (
users.map((user, i) => (
<div key={i}>
<div>email: {user.email}</div>
<div>name: {user.name}</div>
<div>username: {user.username}</div>
<br/>
</div>
))
) : <div>users is empty.</div>}
</div>
)
}
export default List;
接下來你應該會看到很陽春的頁面,如下圖:
新增也運作正常
然後我們寫一下 Model
基底類別
// file: ModelBase.js
import {useState} from "react";
export default class ModelBase {
init = (props, key) => () => {
const [state, setState] = useState(undefined);
Object.assign(this, {
[key]: state,
['set' + key.charAt(0).toUpperCase() + key.substr(1)]: setState
});
if (this[key] === undefined) {
setState(props[key]);
}
}
constructor(props) {
for (let key in props) {
this.init.call(this, props, key)();
}
}
}
User類別
// file: User.js
import ModelBase from "./ModelBase";
export default class User extends ModelBase {
constructor(props = {}) {
super({
email: props.email || '',
name: props.name || '',
username: props.username || '',
password: props.password || '',
});
}
}
稍微描述一下 ModelBase.js
他做了什麼神奇的事情
Hook
這個東西不能在迴圈中使用,也不能在 Class
中使用,所以我們要想辦法繞過他,就產生了下面這一段語法
init = (props, key) => () => {
const [state, setState] = useState(undefined);
Object.assign(this, {
[key]: state,
['set' + key.charAt(0).toUpperCase() + key.substr(1)]: setState
});
if (this[key] === undefined) {
setState(props[key]);
}
}
使用一個函數回傳一個函數,預設給 state 的值為 undefined
這樣的話第一次執行就會把給他的預設值給他 setState
進去。
而 ['set' + key.charAt(0).toUpperCase() + key.substr(1)]
的用處如下:
name => setName
所以這個時候如果你把 user 建立出來在 console.log 出來看一下,會看到:
他不只有 User 類別中定義的那些欄位,還有 useState
回傳的 setState
函數,所以接下來可以把 MyForm.js
改成下面這樣
import User from "../../models/User";
function MyForm(props) {
const user = new User();
return (
<form>
<div>
<label>
Email:
<input type="text" onChange={e => user.setEmail(e.target.value)} defaultValue={user.email}/>
</label>
</div>
<div>
<label>
Name:
<input type="text" onChange={e => user.setName(e.target.value)} defaultValue={user.name}/>
</label>
</div>
<div>
<label>
Username:
<input type="text" onChange={e => user.setUsername(e.target.value)} defaultValue={user.username}/>
</label>
</div>
<div>
<label>
Password:
<input type="password" onChange={e => user.setPassword(e.target.value)}
defaultValue={user.password}/>
</label>
</div>
<div>
<button type="button" onClick={() => props.pushUser(user)}>Submit</button>
</div>
</form>
)
}
export default MyForm;
你可能會覺得,又沒有少什麼東西,行數還是差不多阿!
但是整體邏輯看起來清晰很多,而且因為這只是新增,假設修改要讀入舊資料呢?
因為不想搞得太複雜(處理路由),就先寫死初始資料跟假設我要改第1筆(index=0),所以把 App.js
改成下面這樣
import {useState} from "react";
import MyForm from "./components/MyForm";
import List from "./components/List";
function App() {
const [users, setUsers] = useState([
{email: '[email protected]', name: 'Tom', username: 'admin', password: '1234'}
]);
const pushUser = user => {
const newUsers = [...users];
newUsers.push(user);
setUsers(newUsers);
}
return (
<div>
<MyForm users={users} id={0} pushUser={pushUser}/>
<hr/>
<List users={users}/>
</div>
)
}
export default App;
然後這時你的 MyForm.js
上面就要變成這樣子
function MyForm(props){
const {id} = props;
const user = props.users[id];
const [email, setEmail] = useState(user.email);
const [name, setName] = useState(user.name);
const [username, setUsername] = useState(user.username);
const [password, setPassword] = useState(user.password);
// ...
// 這時的更新函數可能就要丟類似這樣的東西進去 (id, {email, name, username, password})
}
或是這樣子
function MyForm(props){
const {id} = props;
const user = props.users[id];
const [user, setUser] = useState({
email: user.email,
name: user.name,
username: user.username,
password: user.password
});
// ...
// 後面的 onChange 要變成
// onChange={e => setUser({...newUser, name: e.target.value})}
}
如果採用 Class 呢?
function MyForm(props){
const {id} = props;
const user = props.users[id];
const newUser = new User(user);
// ...
// 這時你的更新函數只要丟類似這樣的東西進去 (id, user)
}
簡潔很多,而且假設今天要多一個欄位或少一個欄位,用 Class 只要改類別就可以,不用增減傳入參數,也不會漏掉。
最後更新時間: 2021年07月07日.