公司專案
、readme
、ow-dashboard
本篇以 user login 為例。 |
---|
![]() |
複製整個 src 架構和 .env
, .env.example
, .gitignore
檔案後,清除 src 架構裡的 apis, assets, Containers, routes, Sagas, Stores,執行 npm start
,ok 再進行開發。
檔案名稱 | 回傳 |
---|---|
RouterPage.js (已登入) | return(<HomeLayout>...</HomeLayout>) |
UnRouterPage.js (未登入) | return(<EmptyLayout>...</EmptyLayout>) |
// RouterPage.js (已登入)
class RouterPage extends React.Component {
render() {
return (
<HomeLayout>
...
</HomeLayout>
)
}
}
// UnRouterPage.js (未登入)
const RouterPage = () => (
<EmptyLayout>
...
</EmptyLayout>
);
依照 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>
);
在路徑宣告裡,會提到component={LoginScreen}
,指的是當使用者路徑為某網域/login
時,會在畫面裡先渲染出名為 LoginScreen 的元件(Components),而它包在名為 LoginScreen 的容器(Containers)。
const styles = {title: {color: red,},};
render(){return()}
<div style={styles.title}>...</div>
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);
});
};
LoginMiddleView
和 置右的LoginView
。
render() {
// 回傳
return (
<div style={{ width: '100%', height: '100%' }}>
// 此專案是靠中。
<LoginMiddleView handleLogin={this.handleLogin} />
</div>
);
}
const styles = {title: {color: red,},};
render(){return()}
<div style={styles.title}>...</div>
<Form>
這個 tag,在裡面定義使用者填寫完成時 要做的 function,此 function 是將使用者輸入的值指定給 Containers/LoginScreen.js 的 handleLogin function 回傳的值。
render() {
// 回傳
return (
<Form onFinish={value => handleLogin(value)} >
)}
<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="密碼"
/>
);
}
因為有在登入頁的 <Form>
tag 中,寫 onFinish={value => handleLogin(value)}
,所以會把兩個輸入框的值帶到處理階段。
// 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),
]);
};
// Stores/User/Action.js
const { Types, Creators } = createActions({
login: ['payload', 'callback'],
});
// 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 }) {
...
}
檔案名稱 | 動作用意 | 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 {
...
}
}
順序 | 檔案 | 檔案主用意 |
---|---|---|
1 | InitialState.js |
定義相關值的預設型態 |
2 | Action.js |
將值寫入 |
3 | Reducer.js |
依值是否需要多加處理而分兩種 |
InitialState.js
:定義相關值的預設型態。
// Store/User/InitialState.js
export const INITIAL_STATE = {
Token: '',
user: {},
userList: [],
userInfo: {},
};
Action.js
:將值寫入。
// Store/User/Action.js
import { createActions } from 'reduxsauce';
const { Types, Creators } = createActions({
login: ['payload', 'callback'],
});
export const UserTypes = Types;
export default Creators;
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,
}
};
// apis/User.js -> 對照 Sagas/UserSaga.js 的 User.login()
export default {
// function: () => '指定路徑(來自後端給的)'
login: () => '/api/auth',
};
// 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';
// 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',
}
};
// 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 >
);
}
}