owned this note
owned this note
Published
Linked with GitHub
# REACT 整體架構與開發流程
###### tags:`公司專案`、`readme`、`ow-dashboard`
| <b>本篇以 user login 為例。</b>|
|:--:|
|  |
## ⚑ step1: 初始動作
複製整個 src 架構和 `.env` , `.env.example`, `.gitignore` 檔案後,清除 src 架構裡的 apis, assets, Containers, routes, Sagas, Stores,執行 `npm start`,ok 再進行開發。
## ⚑ step2: 路徑(routes)
### 1. 路徑進入點之檔案區分
| 檔案名稱 | 回傳 |
| -------- | -------- |
| RouterPage.js (已登入) | `return(<HomeLayout>...</HomeLayout>)` |
| UnRouterPage.js (未登入) | `return(<EmptyLayout>...</EmptyLayout>)` |
### 2. 路徑進入點之程式碼擷取
```javascript=
// RouterPage.js (已登入)
class RouterPage extends React.Component {
render() {
return (
<HomeLayout>
...
</HomeLayout>
)
}
}
// UnRouterPage.js (未登入)
const RouterPage = () => (
<EmptyLayout>
...
</EmptyLayout>
);
```
### 3. 路徑進入後之詳細架構:
依照 react-router-dom 原理,寫HashRouter。先判斷路徑、渲染指定的畫面。若沒有指定路徑,則依 react-router-dom 原理,將根目錄導到想要的路徑。
```javascript=
// routes/UnRouterPage.js
const RouterPage = () => (
<EmptyLayout>
// 依react-router-dom原理
<HashRouter>
<Switch>
// 判斷路徑、渲染指定的畫面
<Route path="/login" component={LoginScreen} />
// 依react-router-dom原理,導到想要的/login路徑。
<Redirect from="/" to="/login" />
</Switch>
</HashRouter>
</EmptyLayout>
);
```
## ⚑ step3: 畫面(View)
### 1. 畫面架構:
在路徑宣告裡,會提到`component={LoginScreen}`,指的是當使用者路徑為`某網域/login`時,會在畫面裡先渲染出名為 LoginScreen 的元件(Components),而它包在名為 LoginScreen 的容器(Containers)。
### 2. 畫面架構之容器(Containers):
#### (1) 檔案路徑:Containers/LoginScreen.js
#### (2) 先定義需要的 css style 和 function。如:
```javascript
const styles = {title: {color: red,},};
```
#### (3) 再渲染並回傳內容到指定的地方。 如:
```javascript
render(){return()}
```
#### (4) 定義好的 css style 是用 JavaScript 中 Object 的方式拿取。如:
```javascript
<div style={styles.title}>...</div>
```
#### (5) 定義好的 function
```javascript=
handleLogin = (value) => {
const { login } = this.props;
const { history } = this.props;
// 2. callback function 將 isLoading 一開始的設定給關掉,讓使用者知道已有執行動作。
const callback = () => {
this.setState({
isLoading: false,
});
}
// 1. isLoading 一開始設定為打開、使用者發送請求後就執行api、執行完畢無論結果都會回傳是否成功、回到callback function(#45~49)。
this.setState({
isLoading: true,
}, () => {
login(value, callback);
});
};
```
#### (6) 回傳到指定的地方時,有兩種公司寫好的容器元件。分別是 置中的`LoginMiddleView` 和 置右的`LoginView` 。
```javascript=
render() {
// 回傳
return (
<div style={{ width: '100%', height: '100%' }}>
// 此專案是靠中。
<LoginMiddleView handleLogin={this.handleLogin} />
</div>
);
}
```
### 3. 畫面架構之元件(Components):
#### (1) 檔案路徑:Conponents/LoginMiddleView.js
#### (2) 先定義需要的 css style 和 function。如:
```javascript
const styles = {title: {color: red,},};
```
#### (3) 再渲染並回傳內容到指定的地方。 如:
```javascript
render(){return()}
```
#### (4) 定義好的 css style 是用 JavaScript 中 Object 的方式拿取。如:
```javascript
<div style={styles.title}>...</div>
```
#### (5) 回傳到指定的地方時,
##### ➀使用 `<Form>` 這個 tag,在裡面定義使用者填寫完成時 要做的 function,此 function 是將使用者輸入的值指定給 Containers/LoginScreen.js 的 handleLogin function 回傳的值。
```javascript=
render() {
// 回傳
return (
<Form onFinish={value => handleLogin(value)} >
)}
```
##### ➁基於 AntDesign,公司寫好的元件(Components/common/FormInput.js),使用 `<FormInput>` 這個 tag,做兩個輸入框給使用者填寫帳號與密碼。
```javascript=
render() {
// 回傳
return (
// 基於 Antdesign 的 form,公司寫了 FormInput 這個元件(src/Components/common/FormInput.js)
<FormInput
required
// 用propName 命名
propName='account'
placeholder="帳號"
requiredErrorMessage="帳號"
inputStyle={styles.inputStyle}
/>
<FormInput
required
propName='password'
type="password"
placeholder="密碼"
inputStyle={{ ...styles.inputStyle }}
requiredErrorMessage="密碼"
/>
);
}
```
## ⚑ step4: 動作(action)
### 1. 動作架構之 Saga
#### (1) 原理(緣由)
因為有在登入頁的 `<Form>` tag 中,寫 `onFinish={value => handleLogin(value)}`,所以會把兩個輸入框的值帶到處理階段。
#### (2) Saga 之路徑進入點
- import saga function 和 action 的 type,並 export 它們。概念上是使用 redux-saga 的套件寫法。
```javascript=
// Sagas/index.js
import { takeLatest, all } from 'redux-saga/effects';
// 1. import saga本身
import * as UserSaga from './UserSaga';
// 2. import action的type
import { UserTypes } from 'src/Stores/User/Actions';
export default function* root() {
yield all([
// takeLatest(saga 的 action, 它對應的 function)
takeLatest(UserTypes.LOGIN, UserSaga.login),
]);
};
```
#### (3) Saga function
- 先在 Stores 定義 action
```javascript=
// Stores/User/Action.js
const { Types, Creators } = createActions({
login: ['payload', 'callback'],
});
```
- 再到 Sagas 定義 function
```javascript=
// Saga/UserSaga.js
// 寫 saga 的方式推薦複製整段 export,改變 function 的名稱和傳入值即可。
// 傳入 action 的兩個值(payload, callback)
export function* login({ payload, callback }) {
try {
// yield是來自redux,常用的是yield.call, yield.put
// 定義資料為res(result)。
const { data: res } = yield call(
// call會傳入兩個值:
// 1. api的method(以及帶入會用到的參數,常用的ex:data, token, ContentType)
Handler.post({ data: payload }),
// 2. api的路徑(url)
User.login(),
);
// 寫判斷是看res的status是否為200或success是否為true,是則為成功。
if (res.status === 200) {
const setData = {
Token: res.data.token,
...res.data.userData,
}
saveUserInformation(setData);
// 一定會寫這行,將action的function的data寫回reducer存入。(src/Stores/User/Reducers.js #23~27)
yield put(UserActions.setUser(setData));
// window.location.reload();
}
} catch (err) {
console.log('err', err);
} finally {
if (callback) { callback() }
}
}
```
- 通常會針對主軸,新增一支專屬檔案,如 `UserSaga.js` 。並在該檔案裡,再細分很多 function,如 login, getUserInfo ...等。
```javascript=
// Sagas/UserSaga.js
export function* login({ payload, callback }) {
...
}
export function* getUserInfo({ id, callback }) {
...
}
```
- Saga 處理完後,進到指定的 api。
| 檔案名稱 | 動作用意 | api code |
| -------- | -------- | -------- |
| `Sagas/UserSaga.js` | 呼叫 | `User.login()` |
| `apis/User.js` | 回傳 | `login: () => '/api/auth'` |
```javascript=
// Sagas/UserSaga.js -> 指定的 api 是 User.login()
export function* login({ payload, callback }) {
try {
// yield是來自redux,常用的是yield.call, yield.put
// 定義資料為res(result)。
const { data: res } = yield call(
// call會傳入兩個值:
// 1. api的method(以及帶入會用到的參數,常用的ex:data, token, ContentType)
Handler.post({ data: payload }),
// 2. api的路徑(url)
User.login(),
);
} catch (err) {
...
} finally {
...
}
}
```
### 2. 動作架構之 Store
#### (1) 主要(通常只)會在主功能資料夾放三支 js 檔案,分別為:
| 順序 | 檔案 | 檔案主用意 |
| -------- | -------- | -------- |
| 1 | `InitialState.js` | 定義相關值的預設型態 |
| 2 | `Action.js` | 將值寫入 |
| 3 | `Reducer.js` | 依值是否需要多加處理而分兩種 |
#### (2) `InitialState.js`:定義相關值的預設型態。
```javascript=
// Store/User/InitialState.js
export const INITIAL_STATE = {
Token: '',
user: {},
userList: [],
userInfo: {},
};
```
#### (3) `Action.js`:將值寫入。
```javascript=
// Store/User/Action.js
import { createActions } from 'reduxsauce';
const { Types, Creators } = createActions({
login: ['payload', 'callback'],
});
export const UserTypes = Types;
export default Creators;
```
#### (4) `Reducer.js`:依值是否需要多加處理而分兩種情形。
```javascript=
// Store/User/Reducer.js
// 不多加處理的情形(只拿值作儲存)
export const setUser = (state, { payload }) => ({
...state,
...payload,
user: payload,
});
// 有多加處理的情形
export const getUserListSuccess = (state, { payload, paging }) => {
let temp = [];
payload.map((item) => {
temp.push({
...item,
key: item.user_id,
})
})
return {
// 要記得回傳原本的initial state,否則只會剩新處理的state
...state,
userList: temp,
paging: paging,
}
};
```
### 3. 動作架構之 apis
- 因為使用者發送請求,進到 saga 引導到指定的 api,在 api 檔案裡,前端工程師會將後端工程師給的 api url 指定到 function裡,這就是整個串接 api 的流程。
```javascript=
// apis/User.js -> 對照 Sagas/UserSaga.js 的 User.login()
export default {
// function: () => '指定路徑(來自後端給的)'
login: () => '/api/auth',
};
```
- 使用 postman 串接 api 檔案的方式:將後端工程師給的 api url import 到本機的 postman,postman 會自動顯示他們指定的 method,再按 send 做測試即可。
## ⚑ step4: 切版 ⇾ css framework 使用 [Ant Design](https://ant.design/components/overview-cn/)
### 1. Ant Design 是一個支援 React 的 開源樣式庫。
- [ ] 須先了解組建如何使用,文檔可之後再深入研究。
### 2. 使用開源組件與客製化的方式
- 先在 layout import 需要的元件或檔案來源
```javascript=
// 1. routes/UnRoutePage.js(因為在登入頁有定義 EmptyLayout 是使用 LoginScreen 這個 component)
import LoginScreen from 'src/Containers/Login/LoginScreen';
import EmptyLayout from 'src/Layout/EmptyLayout';
const RouterPage = () => (
<EmptyLayout>
<HashRouter>
<Switch>
<Route path="/login" component={LoginScreen} />
<Redirect from="/" to="/login" />
</Switch>
</HashRouter>
</EmptyLayout>
);
// 2. Containers/Login/LoginScreen.js(在回傳時,指定LoginMiddleView 此款 Components 樣式)
class LoginScreen extends React.Component {
// 渲染的地方
render() {
// 回傳
return (
<div style={{ width: '100%', height: '100%' }}>
{/* 目前用的有兩種是靠中LoginMiddleView款式 */}
<LoginMiddleView handleLogin={this.handleLogin} />
</div>
);
}
}
// 3. Components/LoginMiddleView.js(因為是登入表單,所以引用 Button, Form)
import { Button, Form } from 'antd';
```
- 在細分出的元件或頁面的 js 檔案中,另外自定義需要的 css style。
```javascript=
// Components/LoginMiddleView.js
const styles = {
root: {
height: '100%',
width: '100%',
overflowY: 'hidden',
background: `url(${Images.bg}) no-repeat center bottom`,
backgroundSize: 'cover'
},
btnStyle: {
width: '100px',
height: '40px',
backgroundColor: '#004C7C',
borderRadius: '5px',
color: 'white'
},
inputStyle: {
width: '300px',
height: '40px',
border: '1px solid #CBD871',
borderRadius: '5px',
}
};
```
- 取用需要的 style
```javascript=
// Components/LoginMiddleView.js
class LoginMiddleView extends React.Component {
render() {
return (
// 引用自定義的 css style
<div style={styles.root}>
<div style={styles.wrapperBox}>
<div style={styles.title}>陶朱行網站後台</div>
<div style={styles.middleBox}>
// 自行寫 inline-style
<Form style={{ width: '300px', margin: 'auto', height: '200px', display: 'flex', flexDirection: 'column', justifyContent: 'space-evenly', alignItems: 'center' }}>
...
</Form>
</div>
</div>
</div >
);
}
}
```