1. 布局和样式实现
首先我们实现最基本的布局和样式
// Login.jsx
import React, { PureComponent } from 'react';
import { connect } from 'react-redux';
import { Input, LoginBox, LoginWrapper } from './styles';
import { Button } from './styles';
class Login extends PureComponent {
render() {
return (
<LoginWrapper>
<LoginBox>
<Input placeholder="账号" />
<Input placeholder="密码" />
<Button>登录</Button>
</LoginBox>
</LoginWrapper>
);
}
}
const mapStateToProps = (state) => ({});
const mapDispatchToProps = (dispatch) => ({});
export default connect(mapStateToProps, mapDispatchToProps)(Login);
// styles
import styled from 'styled-components';
export const LoginWrapper = styled.div`
z-index: 0;
position: absolute;
left: 0;
right: 0;
top: 56px;
bottom: 0;
background: #eee;
`;
export const LoginBox = styled.div`
width: 400px;
height: 180px;
margin: 100px auto;
padding-top: 20px;
background: #fff;
box-shadow: 0 0 8px rgba(0, 0, 0, 0.1);
`;
export const Input = styled.input`
display: block;
width: 200px;
height: 30px;
line-height: 30px;
margin: 10px auto;
padding: 0 10px;
color: #777;
`;
export const Button = styled.div`
display: block;
width: 220px;
height: 30px;
line-height: 30px;
margin: 10px auto;
color: #fff;
text-align: center;
background: #3194d0;
border-radius: 15px;
`;
// App中添加到路由中
function App() {
return (
<Provider store={store}>
<BrowserRouter>
<div>
<Header />
<Route path="/" exact component={Home}></Route>
{/*添加:id,访问detail下要求传递一个id参数,后面link跳转时候进行拼接*/}
<Route path="/detail/:id" exact component={Detail}></Route>
<Route path="/login" exact component={Login}></Route>
</div>
</BrowserRouter>
<GlobalIconfont />
<GlobalBody />
</Provider>
);
}
export default App;
2. 逻辑实现
2.1 实现登录
首先实现登录逻辑。当用户尚未登录时,Header组件中会显示登录按钮。点击登录按钮后,跳转到登录页面进行用户信息的填写,验证成功后跳转到首页,此时Header组件中的“登录”会变成“退出”。
让Header组件中的“登录”文字根据是否登录进行变化:
class Header extends Component {
render() {
const { focused, handleInputFocus, handleInputBlur, list, login } = this.props;
return (
<HeaderWrapper>
<Link to={'/'}>
<Logo/>
</Link>
<Nav>
<NavItem className='left active'>首页</NavItem>
<NavItem className='left'>下载App</NavItem>
{
login ?
<NavItem className='right'>退出</NavItem> :
<Link to='/login'><NavItem className='right'>登录</NavItem></Link>
}
<NavItem className='right'>
<i className='iconfont'></i>
</NavItem>
{/* ... */}
</Nav>
</HeaderWrapper>
)
}
}
接下来,实现登录账号的逻辑,使用两个input组件进行账号输入:
import React, { PureComponent } from 'react';
import { connect } from 'react-redux';
import { Input, LoginBox, LoginWrapper } from "./styles";
import { Button } from "./styles";
import * as actionCreators from "./store/actionCreators";
import { Redirect } from "react-router-dom";
class Login extends PureComponent {
render() {
const { loginStatus } = this.props;
if (!loginStatus) {
return (
<LoginWrapper>
<LoginBox>
<Input placeholder='账号' ref={(input) => { this.account = input }} />
<Input placeholder='密码' type='password' ref={(input) => { this.password = input }} />
<Button onClick={() => this.props.login(this.account, this.password)}>登录</Button>
</LoginBox>
</LoginWrapper>
);
} else {
return <Redirect to='/' />;
}
}
}
const mapStateToProps = (state) => ({
loginStatus: state.getIn(['login', 'login'])
});
const mapDispatchToProps = (dispatch) => ({
login(accountElem, passwordElem) {
dispatch(actionCreators.login(accountElem.value, passwordElem.value));
}
});
export default connect(mapStateToProps, mapDispatchToProps)(Login);
使用dispatch派发一个异步action,调用ajax发送请求:
import axios from "axios";
import * as constants from "./constants";
const changeLogin = () => ({
type: constants.CHANGE_LOGIN,
value: true
});
export const login = (account, password) => {
return (dispatch) => {
axios.get('/api/login.json?account=' + account + '&password=' + password)
.then((res) => {
const result = res.data.data;
if (result) {
dispatch(changeLogin());
} else {
alert('登录失败');
}
});
}
};
reducer解析:
2.2 退出逻辑
接下来,我们需要实现退出逻辑。我们需要在 Header 组件中添加一个退出按钮,然后触发 logout 的 action,但是需要注意的是,这个 action 是在 login 组件的 store 中创建的。
代码如下:
{
login ?
<NavItem onClick={logout} className='right'>退出</NavItem> :
<Link to='/login'><NavItem className='right'>登录</NavItem></Link>
}
注意为了区别引用,我们需要在 logout 方法中使用 loginActionCreators 而不是 actionCreators,因为 logout 是在 login 组件的 store 中创建的。
代码如下:
import {actionCreators} from './store/index';
import {actionCreators as loginActionCreators} from '../../pages/login/store/index';
logout(){
dispatch(loginActionCreators.logout());
}
接着,我们需要创建一个 logout 的 action creator:
export const logout = () => ({
type: constants.CHANGE_LOGIN,
value:false
});
最后,我们需要在 reducer 中处理 LOGOUT 的 action:
cCopy code
export default (state = defaultState, action) => {
switch (action.type) {
case constants.CHANGE_LOGIN:
return state.set('login', action.value);
case constants.LOGOUT:
return state.set('login', action.value);
default: {
return state;
}
}
}
2.3 注意事项
注意,如果我们使用styledComponent,我们会发现使用ref获取到的并不是原始DOM元素。这时我们需要使用styledComponent另一种使用方式——innerRef:
const Button = styled.button.attrs({
className: 'red-button'
})`
background: red;
`;
// 在组件中使用innerRef
<Button innerRef={button => this.button = button}>Click me!</Button>
然而,目前的版本已经不再使用innerRef这种调用方式,而是直接兼容ref。因此,我们可以直接使用ref即可:
<Button ref={button => this.button = button}>Click me!</Button>
3. 登录鉴权实现
这里我们实现一个登录鉴权功能,即点击“写笔记”进入写界面需要先登录才能进行。否则,就会进入登录界面。
在Write组件中,我们首先获取login状态:
const {loginStatus} = this.props;
如果已登录,我们渲染写文章页面:
if (loginStatus) {
return (
<div>写文章页面</div>
);
}
否则,我们使用重定向功能将页面更新到登录页面:
return <Redirect to='/login'/>
为了添加“写笔记”按钮,我们在Header组件中添加Link:
<Addition>
<Link to='/write'>
<Button className='writing'>
<i className='iconfont'></i>
写文章
</Button>
</Link>
<Button className='reg'>注册</Button>
</Addition>
同时,我们还需要将login状态映射到Write组件的props中:
const mapStateToProps = (state) => ({
loginStatus: state.getIn(['login', 'login'])
});
export default connect(mapStateToProps, null)(Write);