REACT 整體架構與開發流程

tags:公司專案readmeow-dashboard
本篇以 user login 為例。
REACT 架構之 user login

⚑ 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. 路徑進入點之程式碼擷取

// 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 原理,將根目錄導到想要的路徑。

// 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。如:

const styles = {title: {color: red,},};

(3) 再渲染並回傳內容到指定的地方。 如:

render(){return()}

(4) 定義好的 css style 是用 JavaScript 中 Object 的方式拿取。如:

<div style={styles.title}>...</div>

(5) 定義好的 function

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

render() { // 回傳 return ( <div style={{ width: '100%', height: '100%' }}> // 此專案是靠中。 <LoginMiddleView handleLogin={this.handleLogin} /> </div> ); }

3. 畫面架構之元件(Components):

(1) 檔案路徑:Conponents/LoginMiddleView.js

(2) 先定義需要的 css style 和 function。如:

const styles = {title: {color: red,},};

(3) 再渲染並回傳內容到指定的地方。 如:

render(){return()}

(4) 定義好的 css style 是用 JavaScript 中 Object 的方式拿取。如:

<div style={styles.title}>...</div>

(5) 回傳到指定的地方時,

➀使用 <Form> 這個 tag,在裡面定義使用者填寫完成時 要做的 function,此 function 是將使用者輸入的值指定給 Containers/LoginScreen.js 的 handleLogin function 回傳的值。
render() { // 回傳 return ( <Form onFinish={value => handleLogin(value)} > )}
➁基於 AntDesign,公司寫好的元件(Components/common/FormInput.js),使用 <FormInput> 這個 tag,做兩個輸入框給使用者填寫帳號與密碼。
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 的套件寫法。
// 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
// Stores/User/Action.js const { Types, Creators } = createActions({ login: ['payload', 'callback'], });
  • 再到 Sagas 定義 function
// 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 等。
// 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'
// 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:定義相關值的預設型態。

// Store/User/InitialState.js export const INITIAL_STATE = { Token: '', user: {}, userList: [], userInfo: {}, };

(3) Action.js:將值寫入。

// 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:依值是否需要多加處理而分兩種情形。

// 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 的流程。
// 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

1. Ant Design 是一個支援 React 的 開源樣式庫。

  • 須先了解組建如何使用,文檔可之後再深入研究。

2. 使用開源組件與客製化的方式

  • 先在 layout import 需要的元件或檔案來源
// 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。
// 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
// 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 > ); } }
Select a repo