[React] 18 - Project: Book Worm: Auth
前言
一、故事背景
Project系列,主要是从实践的角度来分析需求,设计组件,设计网页。

二、组件设计纲要
/* implement */
代码分析
一、项目配置问题
- Eslint
.eslintrc文件采用airbnb的严格检查模式。

package.json中添加:
"lint": "eslint src"
- 没用的初始化默认文件

- Function component

快捷键直接得到如下代码模板:

- 偷看颜色

点击后出现颜色值提取器,如下:

填下如下,这里使用了 semanitic-ui。
import React from "react"; import PropTypes from "prop-types"; const InlineError = ({ text }) => ( <span style={{ color: "#ae5856" }}>{text}</span> );
# 参数检查 InlineError.propTypes = { text: PropTypes.string.isRequired }; export default InlineError;
二、组件设计与实现
- BrowserRouter
(1) This requires “yarn add react-router-dom”
[index.js]

(2) 配置主页 HomePage
a. 打算 导入HomePage.js,并作为路由根路径。path = "/" exact
b. 下一步,就是实现HomePage,至少包括了login的链接按钮。
[HomePage.js]

(3) 实现主页 HomePage,以及其中的 Link。
[HomePage.js]

(4) 实现 LoginPage
这里主要是添加上对应的路由。
[App.js]

以上便实现了一个基本的路由过程。
为什么要用exact?
如下情况下,如果匹配路由path='/page',那么会把Home也会展示出来,这就是为什么要使用exact。
<Route path='/' component={Home} /> <Route path='/page' component={Page}>
- LoginPage
(1) semanitic-ui
Ref: https://react.semantic-ui.com/usage
安装并导入:
$ yarn add semantic-ui-react
$ yarn add semantic-ui-css
import 'semantic-ui-css/semantic.min.css';
className的值建立在semanitic-ui上,之后需要系统学习。

(2) LoginForm
render 的内容,首先获得需要的 value:
const { data, errors, loading } = this.state;
然后渲染。
<Form.Field ={!!errors.email}> <label htmlFor="email">Email</label> <input type ="email" id ="email" name ="email" placeholder="example@example.com" value ={data.email} onChange ={this.onChange} /> {errors.email && <InlineError text={errors.email} />} # 判断“有错误”才出现提示
</Form.Field> <Form.Field error={!!errors.password}> <label htmlFor="password">Password</label> <input type ="password" id ="password" name ="password" placeholder="Make it secure" value ={data.password} onChange ={this.onChange} /> {errors.password && <InlineError text={errors.password} />} </Form.Field>
格式错误后出现小提示。

(3) onChange
onChange = e => {
console.log("-->name " + e.target.name)
console.log("-->value " + e.target.value)
this.setState({
data: { ...this.state.data, [e.target.name]: e.target.value }
});
}
a) 如果去掉...this.state.data会报警告如下:

b) 因为我是需要先整体覆盖一次,也就是setState的第一个参数,然后再改变我们想要改变的。
(4) onSubmit
首先要验证下格式;然后再提交登录。
validate = data => {
const errors = {};
if (!Validator.isEmail(data.email)) errors.email = "Invalid email";
if (!data.password) errors.password = "Can't be blank";
return errors;
};
提交操作的实现,如下:
[LoginForm.js]
onSubmit = () => {
const errors = this.validate(this.state.data);
this.setState({ errors });
if (Object.keys(errors).length === 0) { // 格式没问题的话
this.setState({ loading: true });
this.props.submit(this.state.data)
.catch (err =>
this.setState({ errors: err.response.data.errors, loading: false })
);
}
};
submit 真正的实现之地在外层。
[LoginPage.js]
class LoginPage extends React.Component { submit = data => this.props.login(data).then(() => this.props.history.push("/dashboard")); /* 之后会调用到 ./actions/auth.js中的login */
render() { return ( <div> <h1>Login page</h1> <LoginForm submit={this.submit} /> <Link to="/forgot_password">Forgot Password?</Link> </div> ); } }
(5) error={!!errors.email}
双重否定表示的意义是什么? Ref: js双感叹号判断
相当于三元运算符,返回boolean值。
var ret = !!document.getElementById
等价于:
var ret = document.getElementById ? true : false;
当值是非空字符串和非零数字返回true,当值是空字符串、0或者null返回false。
var a = " "; alert(!!a); //true var a = "s"; alert(!!a); //true var a = true; alert(!!a); //true var a = 1; alert(!!a); //true var a = -1; alert(!!a); //true var a = -2; alert(!!a); //true var a = 0; alert(!!a); //false var a = ""; alert(!!a); //false var a = false; alert(!!a); //false var a = null; alert(!!a); //false
三、Redux的应用
Ref: Build Real Web App with React #02
- 导入相关库
[index.js]
import React from "react"; import ReactDOM from "react-dom"; import { BrowserRouter, Route } from "react-router-dom"; import "semantic-ui-css/semantic.min.css"; import { createStore, applyMiddleware } from "redux"; import { Provider } from "react-redux"; import thunk from "redux-thunk"; import decode from "jwt-decode"; import { composeWithDevTools } from "redux-devtools-extension"; import App from "./App"; import registerServiceWorker from "./registerServiceWorker"; import rootReducer from "./rootReducer"; import { userLoggedIn } from "./actions/auth"; import setAuthorizationHeader from "./utils/setAuthorizationHeader";
----------------------------------------------------------------------
const store = createStore( rootReducer, # 树!暂时先不搭建 ---> (2) composeWithDevTools(applyMiddleware(thunk)) # ---> (1) );
----------------------------------------------------------------------
if (localStorage.bookwormJWT) { const payload = decode(localStorage.bookwormJWT); const user = { token: localStorage.bookwormJWT, email: payload.email, confirmed: payload.confirmed }; setAuthorizationHeader(localStorage.bookwormJWT); store.dispatch(userLoggedIn(user)); } ReactDOM.render( <BrowserRouter> <Providerstore={store}> <Route component={App} /> </Provider> </BrowserRouter>, document.getElementById("root") );
registerServiceWorker();
(1) 合并 sub-reducer。
[rootReducer.js]
import { combineReducers } from "redux";
import user from "./reducers/user";
import books from "./reducers/books";
export default combineReducers({
user,
books
});
(2) 实时的监控Redux的状态树的Store
Ref: Redux-devTools简单的使用

- Redux 调用 Login by connect(...)
10:22 / 34:47
[LoginPage.js]
import { connect } from "react-redux"; import { Link } from "react-router-dom"; import LoginForm from "../forms/LoginForm"; import { login } from "../../actions/auth"; class LoginPage extends React.Component { submit = data => this.props.login(data).then(() => this.props.history.push("/dashboard"));
--------------------------------------------------------------------------------
render() { return ( <div> <h1>Login page</h1> <LoginForm submit={this.submit} /> <Link to="/forgot_password">Forgot Password?</Link> </div> ); } }
/**
* shape 类似于包裹着几个属性的 object
*/ LoginPage.propTypes = { history: PropTypes.shape({ push: PropTypes.func.isRequired }).isRequired, login: PropTypes.func.isRequired };
/**
* login 函数就放在了 LoginPage容器 里面
*/ export default connect(null, { login })(LoginPage);
- 实现 Login ---> Server
[actions/auth.js]
export const login = credentials => dispatch =>
api.user.login(credentials).then( # 真正干活的地方
user => {
localStorage.bookwormJWT = user.token;
setAuthorizationHeader(user.token);
dispatch(userLoggedIn(user));
}
);
这里定义了动作信号。
export const userLoggedIn = user => ({
type: USER_LOGGED_IN,
user
});

浙公网安备 33010602011771号