转载请注明出处,未经同意,不可修改文章内容。
🔥🔥🔥“前端一万小时”两大明星专栏——“从零基础到轻松就业”、“前端面试刷题”,已于本月大改版,合二为一,干货满满,欢迎点击公众号菜单栏各模块了解。
1 “登录”功能需求分析
- ❓需求 1:点击“登录”后,跳转至“登录页”;
- ❓需求 2:实现“登录”和“登录验证”的逻辑;
- ❓需求 3:点击“登录”按钮,当“登录验证”成功后,整个页面跳转至“首页”;
- ❓需求 4:且同时,“登录”显示为“退出”;
- ❓需求 5:在登录状态下,点击“退出”,将退出登录状态,并将“退出”显示为“登录”。
2 需求实现
2.1 基本架子搭建
拿到“登录页”,我们首先要考虑它有哪些“数据”。因为,对于一个以“数据”驱动的框架来说,“数据”是我们任何时候都要关注的重点。
1️⃣打开 src 目录 pages 下的文件夹 login ,login 下会有很多只属于 Login 组件的“数据”(至少会有一个“登录”与否的状态—— login: true/false ),我们就可以将这些“数据”放在 login 里进行管理。
在 login 目录下新建一个文件夹 store :
2️⃣紧接着,在 store 文件夹下创建一个 index.js 文件,它的作用为——❗️它是这整个 store 的“出口”文件(“出口”文件的好处为:它可以简化引用文件时的“路径”,也便于后期维护~):
3️⃣在 store 文件夹下创建一个 reducer.js 文件,让 reducer.js 来管理 login 下的“数据”:
4️⃣同理,在 store 文件夹下创建 actionTypes.js 和 actionCreators.js 文件备用:
小架子搭起后,我们按流程编写各文件里的代码。
5️⃣编写 reducer.js 中的代码:
5️⃣-①: reducer.js 返回一个“函数”;
import {fromJS} from "immutable"; /*
❗️从 immutable 中引入 fromJS 方法,这个“方法”可以
帮我们把一个“JS 对象”转化为一个“immutable 对象”!
*/
const defaultState = fromJS(
);
export default (state=defaultState, action) => {
return state;
}
5️⃣-②:将 Login 组件的“登录状态”( true 或 false )这个“数据”放到 reducer.js 中进行管理;
import {fromJS} from "immutable";
const defaultState = fromJS({
login: false // ❗️初始为“未登录”!
});
export default (state=defaultState, action) => {
return state;
}
5️⃣-③:打开 login 目录下 store 中的“出口”文件 index.js ,定义一些通用的需要传递出去的东西;
import reducer from "./reducer";
import * as actionTypes from "./actionTypes";
import * as actionCreators from "./actionCreators";
export {reducer, actionTypes, actionCreators};
❗️5️⃣-④:返回 src 目录下 store 中的 reducer.js 文件,引入上一步创建的 reducer;
import {combineReducers} from "redux-immutable";
import {reducer as headerReducer} from "../common/header/store";
import {reducer as homeReducer} from "../pages/home/store";
import {reducer as detailReducer} from "../pages/detail/store";
import {reducer as loginReducer} from "../pages/login/store"; // ❗️❗️❗️
const reducer = combineReducers({
header: headerReducer,
home: homeReducer,
detail: detailReducer,
login: loginReducer // ❗️❗️❗️
})
export default reducer;
返回页面控制台查看是否有“数据”了(“数据”已存在):
6️⃣OK,通过以上步骤,store 里边就有“数据”了。按照 Redux 和 React-redux 的工作流程,一旦有了数据,Login 组件就可以连接 store,去 store 里边取数据并进行后续一系列的逻辑操作了!
6️⃣-①:按照流程,这一步应该是让“Login 组件”拥有获取 store 中数据的“能力”。但这一步,我们在《登录页开发——① 登录页面布局》中已经实现了——我们已经把 Login 组件放到了 Provider 组件之中。
打开 src 目录下的 App.js 文件查看:
import React, { Component } from "react";
import {GlobalStyle} from "./style";
import {GlobalIconStyle} from "./statics/iconfont/iconfont";
import {BrowserRouter, Route} from "react-router-dom";
import Header from "./common/header";
import Home from "./pages/home";
import Detail from "./pages/detail";
import Login from "./pages/login";
import { Provider } from "react-redux";
import store from "./store";
class App extends Component {
render() {
return (
<div>
<GlobalStyle />
<GlobalIconStyle />
<Provider store={store}>
<BrowserRouter>
<div>
<Header />
<Route path="/" exact component={Home}></Route>
<Route path="/detail/:id" exact component={Detail}></Route>
<Route path="/login" exact component={Login}></Route> {/* ❗️❗️❗️ */}
</div>
</BrowserRouter>
</Provider>
</div>
);
}
}
export default App;
OK,“能力”也有了,接下来我们就开始挨个实现上边提的需求!
2.2 需求 1——点击“登录”后,跳转至“登录页”
1️⃣打开 header 目录下的 index.js 文件:
import React, {Component} from "react";
import {Link} from "react-router-dom";
import {
HeaderWrapper,
Logo,
Navbar,
ItemList,
LinkList,
SearchArea,
SearchInput,
SearchPanel,
PanelTitle,
PanelChange,
PanelLabels,
LabelLink,
Extra,
ExtraLink
} from "./style";
import { connect } from "react-redux";
import {actionCreators} from "./store";
class Header extends Component {
getPanels() {
const newList = this.props.list.toJS();
const pageLabels = [];
if(newList.length) {
for(let i=(this.props.page - 1)*10; i<this.props.page*10; i++) {
pageLabels.push(
<Link to="/" key={newList[i]}>
<LabelLink>
{newList[i]}
</LabelLink>
</Link>
)
}
return pageLabels;
}
}
render() {
return (
<HeaderWrapper>
<Link to="/">
<Logo>
<img src="https://qdywxs.github.io/jianshu-images/logo.png" alt="logo" />
</Logo>
</Link>
<Navbar className="clearfix">
<ItemList className="active">
<Link to="/">
<LinkList href="/">
首页
</LinkList>
</Link>
</ItemList>
<ItemList>
<Link to="/">
<LinkList>
下载APP
</LinkList>
</Link>
</ItemList>
</Navbar>
<SearchArea>
<SearchInput
onFocus={() => this.props.handleInputFocus(this.props.list)}
/>
<span className="iconfont icon-search"></span>
<SearchPanel>
<PanelTitle>
热门搜索
<PanelChange
onMouseDown={this.props.handleMouseDown}
onMouseUp={this.props.handleMouseUp}
onClick={() => this.props.handleChangePage(this.props.page, this.props.totalPage)}
>
<span className={this.props.refresh ? "iconfont refresh" : "iconfont"}></span>
换一批
</PanelChange>
</PanelTitle>
<PanelLabels className="clearfix">
{this.getPanels()}
</PanelLabels>
</SearchPanel>
</SearchArea>
<Extra>
<span className="iconfont icon-textsize" ></span>
{/*
❗️❗️❗️2️⃣将“登录”的 Link 标签的跳转“路径”改为 /login;
先注释掉下面的写法~
<Link to="/">
*/}
<Link to="/login">
<ExtraLink className="login">
登录
</ExtraLink>
</Link>
<Link to="/">
<ExtraLink className="register">
注册
</ExtraLink>
</Link>
<Link to="/">
<ExtraLink className="writing">
<span className="iconfont icon-pen"></span>
写文章
</ExtraLink>
</Link>
</Extra>
</HeaderWrapper>
)
}
}
const mapStateToProps = (state) => {
return {
refresh: state.getIn(["header", "refresh"]),
list: state.getIn(["header", "list"]),
page: state.getIn(["header", "page"]),
totalPage: state.getIn(["header", "totalPage"])
}
}
const mapDispatchToProps = (dispatch) => {
return {
handleMouseDown() {
const action = actionCreators.changeClassNameAction();
dispatch(action)
},
handleMouseUp() {
const action = actionCreators.resumeClassNameAction();
dispatch(action)
},
handleInputFocus(list) {
if(list.size === 0) {
const action = actionCreators.initLabelAction();
dispatch(action)
}
},
handleChangePage(page, totalPage) {
if(page < totalPage) {
dispatch(actionCreators.changePageAction(page + 1))
}else {
dispatch(actionCreators.changePageAction(1))
}
}
}
}
export default connect(mapStateToProps, mapDispatchToProps)(Header);
返回页面查看(点击“登录”时,成功跳转至 login 页):
2.3 需求 2——实现“登录”和“登录验证”的逻辑
🏆“登录”的背后逻辑为:我们需要发送“异步”请求调用 AJAX 接口,将用户输入的“账户”和“密码”传递给后端,然后后端进行比对:
- 一致,则可以登录;
- 不一致,则“登录”失败。
1️⃣mock 数据——在项目 public 目录下 api 文件夹中新增一个 loginData.json 文件:
2️⃣编写 mock 数据 loginData.json 中的内容(❗️我们默认任何“账户”和“密码”都能登录成功):
{
"success": true,
"data": true
}
3️⃣打开 login 目录下的 index.js 文件:
❓❓❓我们需要思考一个问题——要做“登录验证”,即用用户输入的“账号”和“密码”来跟后端存储的“账号”和“密码”作比对。可怎样才能获取到用户输入的“账户”和“密码”呢?
答:
🔗前置知识:《React 进阶——⑤ React 中 ref 的使用》
想要获取用户的输入值,我们很容易能想到:
- 获取到“账号”和“密码”这两个 input 框的 DOM 节点;
- 有了 DOM 节点,我们就能很容易得到 input 框
value值——用户输入的“账号”、“密码”。
3️⃣-①:按“前置知识”,我们应该以如下方式使用 ref 来获取到 input 框的 DOM 节点;
import React, {Component} from "react";
import {
LoginWrapper,
LoginBox,
Input,
Button
} from "./style"
class Login extends Component {
render() {
return(
<LoginWrapper>
<LoginBox>
{/*
❗️3️⃣-②:首先,给 input 元素增加一个 ref 属性,它等于一个“箭头函数”;
“箭头函数”的格式为:
3️⃣-②-1:箭头的左边,“箭头函数”会自动接收一个“参数”,名字可以随便取(我取名为 input);
3️⃣-②-2:箭头的右边,是一个固定的写法 this.text = input,即“账号”等于 input;
3️⃣-②-3:❗️❗️❗️这整行代码的意思为——我构建一个“引用”ref,这个“引用”叫做
this.text,它指向 input 框对应的 DOM 节点。
*/}
<Input type="text" placeholder="账号" ref={(input) => {this.text = input}} />
{/* ❗️3️⃣-③:同理,给“密码框”的 input 元素也增加一个 ref 属性! */}
<Input type="password" placeholder="密码" ref={(input) => {this.password = input}} />
<Button type="submit">登陆</Button>
</LoginBox>
</LoginWrapper>
)
}
}
export default Login;
🏆如此一来, this.text.value 和 this.password.value 就分别表示用户输入的“账号”和“密码”。
4️⃣既然拿到了用户的输入“值”,接下就又是发送“异步”请求、修改“数据”的“套路”了,继续在 login 目录下的 index.js 文件中编码:
import React, {Component} from "react";
import {
LoginWrapper,
LoginBox,
Input,
Button
} from "./style"
/*
4️⃣-①:从 react-redux 中引入 connect 方法(它也是 React-redux 的核心 API 之一),
connect 的目的很明确——就是“连接”的意思!
*/
import {connect} from "react-redux";
// ❗️❗️❗️引入 actionCreators!
import {actionCreators} from "./store";
class Login extends Component {
render() {
return(
<LoginWrapper>
<LoginBox>
<Input type="text" placeholder="账号" ref={(input) => {this.text = input}} />
<Input type="password" placeholder="密码" ref={(input) => {this.password = input}} />
{/*
5️⃣给“登录”按钮绑定一个“点击”事件,
❓可这个“事件”应该怎样被调用呢?
*/}
{/*
5️⃣-②:因此,这里可以通过 this.props.handleLogin 来调用
store 中的 handleLogin;
❗️❗️❗️同时,通过“箭头函数”将“登录”和“密码”对应的“DOM 节点”传递出去!
*/}
<Button type="submit" onClick={() => this.props.handleLogin(this.text, this.password)}>登陆</Button>
</LoginBox>
</LoginWrapper>
)
}
}
/*
❗️❗️❗️4️⃣-⑤:接下来,我们定义哪些“用户的操作”
应该当作 action,并传给 store;
*/
const mapDispatchToProps = (dispatch) => { /*
4️⃣-⑥:把 store 里的“dispatch 方法”作为
参数传递给 mapDispatchToProps;
*/
return {
handleLogin(accountElem, passwordElem) { /*
❗️5️⃣-①:在这里定义 handleLogin 会被当作 action
传给 store;
*/
/*
5️⃣-③:相应地,handleLogin 就会接收到
两个参数,accountElem 和 passwordElem;
*/
/*
5️⃣-④:Redux-thunk 中,“异步”代码我们是放在 action 中进行。这里
我们仅作方法的“调用”;
❗️❗️❗️当然,“参数”也需要传递过去!这里传递给后端验证的“参数”是
用户在 input 框真正输入的“值”,故“参数”为 accountElem.value
和 passwordElem.value。
❗️注意一定要在本文件上边去引入 actionCreators!
*/
const action = actionCreators.loginAction(accountElem.value, passwordElem.value);
dispatch(action);
}
}
}
/*
❗️4️⃣-②:之前我们直接导出的是 Login,可用了 React-redux 后,就不能这样写了!
export default Login;
*/
/*
4️⃣-③:取而代之,我们是导出 connect 方法(
❗️注意看我们给 connect 方法传递了哪些参数!);
*/
export default connect(null, mapDispatchToProps)(Login); /*
4️⃣-④:我们一共要给 connect 传递 3 个参数!
Login 表示:connect 会让 “Login 组件”和store
进行“连接”;
null 表示:这里还会接收一个名叫
mapStateToProps 的参数,由于这里暂时不需要去
获取“数据”,故用 null 占位;
mapDispatchToProps 表示:我们把 store 的
dispatch 方法“挂载”到 Login 组件的 props 上。
即,我们可以定义哪些“用户的操作”应该当作 action,
并传给 store!
*/
5️⃣-⑤:打开 login 目录下 store 中的 actionCreators.js 文件,定义这个 action;
🔗前置知识:《HTML——③ HTML 表单详解》——弄懂 get 和 post 的区别,以及当我们点“登录”这个按钮时,浏览器做了什么事情!
// 5️⃣-⑦:引入 axios 模块;
import axios from "axios";
// 5️⃣-⑥:在 action 中添加 AJAX“异步”代码(❗️❗️❗️注意接收那两个“参数”);
export const loginAction = (account, password) => {
// 5️⃣-⑧:编写“异步”函数进行“登录验证”(❗️❗️❗️既然是“验证”,我们就得给后端带几个“参数”过去);
return(dispatch) => {
axios.get("/api/loginData.json?account=" + account + "&password=" + password)
// ❗️❗️❗️当然,实际上是应该通过 POST 的方式向后端“传数据”,但这里为了演示的方便,我们选用 GET!
.then((res) => {
const result = res.data.data;
})
.catch(() => {alert("error")})
}
}
返回页面查看,输入“账号”和“密码”,点击“登录”,看看“验证”的情况(成功验证):
6️⃣-①:打开 login 目录下 store 中的 actionTypes.js 文件;
// ❗️定义好常量~
export const CHANGE_LOGIN_DATA_ACTION = "change_login_data_action";
6️⃣-②:返回 login 目录下 store 中的 actionCreators.js 文件;
import axios from "axios";
// 6️⃣-③:先引入“常量”;
import {CHANGE_LOGIN_DATA_ACTION} from "./actionTypes";
// 6️⃣-⑥:在这里定义这个“同步”的 action;
const changeLoginDataAction = () => ({
type: CHANGE_LOGIN_DATA_ACTION,
login: true
})
export const loginAction = (account, password) => {
return(dispatch) => {
axios.get("/api/loginData.json?account=" + account + "&password=" + password)
.then((res) => {
const result = res.data.data;
// 6️⃣-④:获取到数据后,可以通过一个条件判断来适时修改 login 的值;
if(result) { /*
6️⃣-⑤:从我们 mock 的数据来看,无论输入任何字符都可以登录成功。
所以走下边这个的流程;
*/
const action = changeLoginDataAction();
dispatch(action); // 6️⃣-⑦:将这个 action 发送给 reducer!
}else {
alert("登录失败!")
}
})
.catch(() => {alert("error")})
}
}
7️⃣打开 login 目录下 store 中的 reducer.js 文件:
import {fromJS} from "immutable";
// 7️⃣-①:先引入“常量”;
import {CHANGE_LOGIN_DATA_ACTION} from "./actionTypes";
const defaultState = fromJS({
login: false // ❗️初始为“未登录”!
});
export default (state=defaultState, action) => {
// 7️⃣-②:编写替换“数据”的逻辑;
if(action.type === CHANGE_LOGIN_DATA_ACTION) {
return state.set("login", action.login);
}
return state;
}
返回页面查看(“数据”确实变了——用户输入“账号”和“密码”,然后点击“登录”后,数据 login 的值从 false 变为了 true ):
2.4 “需求 3”和“需求 4”——成功验证后,进行“页面”的“状态”的相应跳转
2.4.1 需求 3——点击“登录”按钮,当“登录验证”成功后,整个页面跳转至“首页”
🏆需求分析:怎么调回至“首页”呢?
- 在 login 目录下的
index.js中,通过 mapStateToProps 将 store 里的“数据”映射到 Login 组件的 props 上; - 接着,在 Login 组件的“render 函数” 作一个条件判断——当“登录”状态为
false时,才渲染相关“样式组件”;否则(“登录”状态为true时),跳转至“首页”。
1️⃣打开 login 目录下的 index.js 文件:
import React, {Component} from "react";
import {
LoginWrapper,
LoginBox,
Input,
Button
} from "./style"
import {connect} from "react-redux";
import {actionCreators} from "./store";
/*
❗️❗️❗️1️⃣-⑨:幸运的是,react-router-dom 为我们提供了一个“组件”——Redirect,
它可以直接“重定向”至目的页;
*/
import {Redirect} from "react-router-dom";
class Login extends Component {
render() {
/*
❗️❗️❗️1️⃣-⑤:“映射”上了过后,我们就可以利用 this.props.login 来做一个
条件判断;
注释掉下面的代码~
return(
<LoginWrapper>
<LoginBox>
<Input type="text" placeholder="账号" ref={(input) => {this.text = input}} />
<Input type="password" placeholder="密码" ref={(input) => {this.password = input}} />
<Button type="submit" onClick={() => this.props.handleLogin(this.text, this.password)}>登陆</Button>
</LoginBox>
</LoginWrapper>
)
*/
if(!this.props.login) { // ❗️❗️❗️1️⃣-⑥:当“未登录”(未通过验证)时,渲染以下“样式组件”;
return(
<LoginWrapper>
<LoginBox>
<Input type="text" placeholder="账号" ref={(input) => {this.text = input}} />
<Input type="password" placeholder="密码" ref={(input) => {this.password = input}} />
<Button type="submit" onClick={() => this.props.handleLogin(this.text, this.password)}>登陆</Button>
</LoginBox>
</LoginWrapper>
)
}else { // ❗️1️⃣-⑦:否则执行以下逻辑(重定向到“首页”);
// ❓1️⃣-⑧:怎样“重定向”到首页呢?
// 1️⃣-⑩:我们用“Redirect 组件”来“重定向”至“首页”;
return <Redirect to="/" />
}
}
}
// 1️⃣-②:接下来,我们先定义“连接”的规则;
const mapStateToProps = (state) => ({ /*
1️⃣-③:把 store 里的“数据 state”作为“参数”
传递给 mapStateToProps;
*/
login: state.getIn(["login", "login"]) /*
❗️1️⃣-④:规则的具体做法为——将 store 里
的 login 映射到“Login 组件”里的 props
的 login 中去;
*/
})
const mapDispatchToProps = (dispatch) => {
return {
handleLogin(accountElem, passwordElem) {
const action = actionCreators.loginAction(accountElem.value, passwordElem.value);
dispatch(action);
}
}
}
/*
❗️1️⃣-①:给 connect 传递另一个重要“参数”mapStateToProps。
根据上边一系列的操作,Login 组件已经有“能力”取得 store 中的“数据”,
有“能力”后,Login 组件就可以去“连接”store,并从 store 中去取“数据”了。
但“连接”是需要“规则”的,而具体的“规则”就在这个 mapStateToProps 里面(❗️直译为:把 store 里
的“数据 state”映射到“Login 组件”的 props 里)!
注释掉下面这行代码~
export default connect(null, mapDispatchToProps)(Login);
*/
export default connect(mapStateToProps, mapDispatchToProps)(Login);
返回页面查看(需求得以正确实现):
2.4.2 需求 4——且同时,“Header 组件”里的“登录”显示为“退出”
2️⃣打开 header 目录下的 index.js 文件:
import React, {Component} from "react";
import {Link} from "react-router-dom";
import {
HeaderWrapper,
Logo,
Navbar,
ItemList,
LinkList,
SearchArea,
SearchInput,
SearchPanel,
PanelTitle,
PanelChange,
PanelLabels,
LabelLink,
Extra,
ExtraLink
} from "./style";
import { connect } from "react-redux";
import {actionCreators} from "./store";
class Header extends Component {
getPanels() {
const newList = this.props.list.toJS();
const pageLabels = [];
if(newList.length) {
for(let i=(this.props.page - 1)*10; i<this.props.page*10; i++) {
pageLabels.push(
<Link to="/" key={newList[i]}>
<LabelLink>
{newList[i]}
</LabelLink>
</Link>
)
}
return pageLabels;
}
}
render() {
return (
<HeaderWrapper>
<Link to="/">
<Logo>
<img src="https://qdywxs.github.io/jianshu-images/logo.png" alt="logo" />
</Logo>
</Link>
<Navbar className="clearfix">
<ItemList className="active">
<Link to="/">
<LinkList href="/">
首页
</LinkList>
</Link>
</ItemList>
<ItemList>
<Link to="/">
<LinkList>
下载APP
</LinkList>
</Link>
</ItemList>
</Navbar>
<SearchArea>
<SearchInput
onFocus={() => this.props.handleInputFocus(this.props.list)}
/>
<span className="iconfont icon-search"></span>
<SearchPanel>
<PanelTitle>
热门搜索
<PanelChange
onMouseDown={this.props.handleMouseDown}
onMouseUp={this.props.handleMouseUp}
onClick={() => this.props.handleChangePage(this.props.page, this.props.totalPage)}
>
<span className={this.props.refresh ? "iconfont refresh" : "iconfont"}></span>
换一批
</PanelChange>
</PanelTitle>
<PanelLabels className="clearfix">
{this.getPanels()}
</PanelLabels>
</SearchPanel>
</SearchArea>
<Extra>
<span className="iconfont icon-textsize" ></span>
{/*
❗️2️⃣-②:通过“三元运算符”的方式,作一个判断——当 this.props.login 为 true 时(
即,“登录状态”),显示“退出”;否则(未登录时),显示“登录”!
*/}
{this.props.login ? <ExtraLink className="login">退出</ExtraLink> :
<Link to="/login">
<ExtraLink className="login">
登录
</ExtraLink>
</Link>
}
<Link to="/">
<ExtraLink className="register">
注册
</ExtraLink>
</Link>
<Link to="/">
<ExtraLink className="writing">
<span className="iconfont icon-pen"></span>
写文章
</ExtraLink>
</Link>
</Extra>
</HeaderWrapper>
)
}
}
const mapStateToProps = (state) => {
return {
refresh: state.getIn(["header", "refresh"]),
list: state.getIn(["header", "list"]),
page: state.getIn(["header", "page"]),
totalPage: state.getIn(["header", "totalPage"]),
/*
❗️❗️❗️2️⃣-①:由于 Header 组件早就和 store 建立好了“连接”,
这里我们就可以直接从 login 里取到“数据”项 login,并“映射”
到 Header 组件里的 props 的 login 中去;
*/
login: state.getIn(["login", "login"])
}
}
const mapDispatchToProps = (dispatch) => {
return {
handleMouseDown() {
const action = actionCreators.changeClassNameAction();
dispatch(action)
},
handleMouseUp() {
const action = actionCreators.resumeClassNameAction();
dispatch(action)
},
handleInputFocus(list) {
if(list.size === 0) {
const action = actionCreators.initLabelAction();
dispatch(action)
}
},
handleChangePage(page, totalPage) {
if(page < totalPage) {
dispatch(actionCreators.changePageAction(page + 1))
}else {
dispatch(actionCreators.changePageAction(1))
}
}
}
}
export default connect(mapStateToProps, mapDispatchToProps)(Header);
返回页面查看(“退出”正常显示):
2.5 需求 5——在登录状态下,点击“退出”,将退出登录状态,并将“退出”显示为“登录”
1️⃣打开 header 目录下的 index.js 文件:
import React, {Component} from "react";
import {Link} from "react-router-dom";
import {
HeaderWrapper,
Logo,
Navbar,
ItemList,
LinkList,
SearchArea,
SearchInput,
SearchPanel,
PanelTitle,
PanelChange,
PanelLabels,
LabelLink,
Extra,
ExtraLink
} from "./style";
import { connect } from "react-redux";
import {actionCreators} from "./store";
// ❗️❗️❗️1️⃣-⑤:从正确的位置引入 actionCreators!
import {actionCreators as loginActionCreators} from "../../pages/login/store";
class Header extends Component {
getPanels() {
const newList = this.props.list.toJS();
const pageLabels = [];
if(newList.length) {
for(let i=(this.props.page - 1)*10; i<this.props.page*10; i++) {
pageLabels.push(
<Link to="/" key={newList[i]}>
<LabelLink>
{newList[i]}
</LabelLink>
</Link>
)
}
return pageLabels;
}
}
render() {
return (
<HeaderWrapper>
<Link to="/">
<Logo>
<img src="https://qdywxs.github.io/jianshu-images/logo.png" alt="logo" />
</Logo>
</Link>
<Navbar className="clearfix">
<ItemList className="active">
<Link to="/">
<LinkList href="/">
首页
</LinkList>
</Link>
</ItemList>
<ItemList>
<Link to="/">
<LinkList>
下载APP
</LinkList>
</Link>
</ItemList>
</Navbar>
<SearchArea>
<SearchInput
onFocus={() => this.props.handleInputFocus(this.props.list)}
/>
<span className="iconfont icon-search"></span>
<SearchPanel>
<PanelTitle>
热门搜索
<PanelChange
onMouseDown={this.props.handleMouseDown}
onMouseUp={this.props.handleMouseUp}
onClick={() => this.props.handleChangePage(this.props.page, this.props.totalPage)}
>
<span className={this.props.refresh ? "iconfont refresh" : "iconfont"}></span>
换一批
</PanelChange>
</PanelTitle>
<PanelLabels className="clearfix">
{this.getPanels()}
</PanelLabels>
</SearchPanel>
</SearchArea>
<Extra>
<span className="iconfont icon-textsize" ></span>
{/*
❗️1️⃣-①:给“退出”按钮绑定一个“点击”事件,
❓可这个“事件”应该怎样被调用呢?
*/}
{/*
1️⃣-③:因此,这里可以通过 this.props.logout 来
调用 store 中的 logout;
*/}
{this.props.login ? <ExtraLink className="login" onClick={this.props.logout}>退出</ExtraLink> :
<Link to="/login">
<ExtraLink className="login">
登录
</ExtraLink>
</Link>
}
<Link to="/">
<ExtraLink className="register">
注册
</ExtraLink>
</Link>
<Link to="/">
<ExtraLink className="writing">
<span className="iconfont icon-pen"></span>
写文章
</ExtraLink>
</Link>
</Extra>
</HeaderWrapper>
)
}
}
const mapStateToProps = (state) => {
return {
refresh: state.getIn(["header", "refresh"]),
list: state.getIn(["header", "list"]),
page: state.getIn(["header", "page"]),
totalPage: state.getIn(["header", "totalPage"]),
login: state.getIn(["login", "login"])
}
}
const mapDispatchToProps = (dispatch) => {
return {
handleMouseDown() {
const action = actionCreators.changeClassNameAction();
dispatch(action)
},
handleMouseUp() {
const action = actionCreators.resumeClassNameAction();
dispatch(action)
},
handleInputFocus(list) {
if(list.size === 0) {
const action = actionCreators.initLabelAction();
dispatch(action)
}
},
handleChangePage(page, totalPage) {
if(page < totalPage) {
dispatch(actionCreators.changePageAction(page + 1))
}else {
dispatch(actionCreators.changePageAction(1))
}
},
// ❗️❗️❗️1️⃣-②:在这里定义 logout 会被当作 action 传给 store;
logout() {
/*
❓1️⃣-④:接下来,我们需要在这里“调用” actionCreators 里定义的 action,但由于这个
action 是去改变 Login 组件里的“数据”(login 值),所以我们要从 login 页面里的
actionCreators 里引入 action!
*/
// 1️⃣-⑥:调用 loginActionCreators 里的 logout;
const action = loginActionCreators.logout();
dispatch(action);
}
}
}
export default connect(mapStateToProps, mapDispatchToProps)(Header);
2️⃣请记得去 login 目录下 store 中的 actionTypes.js 和 actionCreators.js 文件中分别定义“常量”和 action:
actionTypes.js
export const CHANGE_LOGIN_DATA_ACTION = "change_login_data_action";
// ❗️定义好常量~
export const LOGOUT = "logout";
actionCreators.js
import axios from "axios";
// 2️⃣-①:先引入“常量”;
import {CHANGE_LOGIN_DATA_ACTION, LOGOUT} from "./actionTypes";
const changeLoginDataAction = () => ({
type: CHANGE_LOGIN_DATA_ACTION,
login: true
})
export const loginAction = (account, password) => {
return(dispatch) => {
axios.get("/api/loginData.json?account=" + account + "&password=" + password)
.then((res) => {
const result = res.data.data;
if(result) {
const action = changeLoginDataAction();
dispatch(action);
}else {
alert("登录失败!")
}
})
.catch(() => {alert("error")})
}
}
// 2️⃣-②:定义 action;
export const logout = () => ({
type: LOGOUT,
login: false
})
3️⃣打开 login 目录下 store 中的 reducer.js 文件:
import {fromJS} from "immutable";
// 3️⃣-①:先引入“常量”;
import {CHANGE_LOGIN_DATA_ACTION, LOGOUT} from "./actionTypes";
const defaultState = fromJS({
login: false // ❗️初始为“未登录”!
});
export default (state=defaultState, action) => {
if(action.type === CHANGE_LOGIN_DATA_ACTION) {
return state.set("login", action.login);
};
if(action.type === LOGOUT) {
// 3️⃣-②:编写替换“数据”的逻辑;
return state.set("login", action.login);
}
return state;
}
返回页面查看(“退出”功能正常运行):
本篇篇幅较长,但主要还是跑“流程”,本篇一定要掌握,因为 React 编程的“套路”都在这里边运用上了。
祝好,qdywxs ♥ you!