preface
Team work to copy the mobile APP of Ele. me and choose the popular React framework. Although the function of the project is not perfect, most of the main knowledge points of React are covered in the development process, which is suitable for beginners to get familiar with the framework and get started quickly. I am mainly responsible for the discovery page, order page and login page. Below, I briefly summarize the functions and knowledge points carried by each page, and also sort out the problems encountered in the process of use, which is a very pleasant learning process.
The project has been uploaded to Github, welcome everyone to download and exchange.
Front-end project address: github.com/Hanxueqing/…
Background data address: github.com/Hanxueqing/…
Online project handbook: hanxueqing. Making. IO/React – Eleme…
Project access address: http://39.96.84.220/react-ele/#/home
Project technology stack
- React.js family buckets: react-router, Redux, redux-thunk
- Add-ons: image LazyLoad, video plugin video-react
- Immutable. Js library
- Font awesome icon library
- UI: ANTD-Mobile component library
- Axios asynchronously requests remote data
- Background interfaces: Express and MongoDB. For information on how to make data interfaces, see my previous article, “A Front-end Siege lion who Can’t Write API data interfaces is not a Good programmer.”
- Package online: Ali cloud server. For details on how to apply for, configure ali Cloud server, and package the project online, please refer to my previous article: “From 0 to 1: The whole Process of Ali Cloud Server Deployment Web Project”.
Project running
# cloning to local git clone [email protected]: Hanxueqing/React - Eleme. Git # installation depend on NPM install # open local server localhost yarn yarn start # release environment buildCopy the code
Project development
1. Create projects
Install create-React-app scaffolding globally
cnpm install create-react-app -g
Copy the code
Install the YARN tool globally
yarn npm install -g yarn
Copy the code
Generate a React development template in the eleme directory
create-react-app eleme
Copy the code
When you need to perform secondary configuration, you need to find the react-scripts folder in node_modules for configuration. When you run YARN eject, you can extract the configuration file to facilitate configuration development.
Yarn eject(Extract configuration files for subsequent development)Copy the code
Install redux, redux-thunk, React-redux, and AXIos modules
yarn add redux redux-thunk react-redux axios -S
Copy the code
Installation node – sass
yarn add node-sass -D
Copy the code
Sometimes node_modules package conflicts and needs to be removed and reinstalled via CNPM I *
NPM I and YARN Install cannot be installed and Node-sass installation is slow. Please refer to this article: blog.csdn.net/qq_14988399…
2. Build projects
2-1 style Related configuration
Create in the SRC /stylesheets/ styles folder:
_base.scss
_commons.scss
_reset.scss
_mixins.scss
Import the created SCSS file in the main. SCSS file
@import "_base.scss";
@import "_reset.scss";
@import "_mixins.scss";
@import "_commons.scss";
Copy the code
2-2 Store configuration (It is recommended that all data be managed in Redux)
SRC/store/index. Js file
import {createStore,applyMiddleware} from "redux"
import reducer from "./reducer"
import thunk from "redux-thunk" // This middleware action=> The internal functions of the process reached reducer can realize asynchronous operations, so it is said that the diaptch function is enhanced
const store = createStore(reducer, applyMiddleware(thunk));// Asynchronous requests are available in the project actionCreators
export default store;
Copy the code
SRC/store/reducer. Js file
// This is a reduced reducer
import {combineReducers} from "redux"
import commons from "./commons/reducer"
const reducer = combineReducers({
commons
})
export default reducer;
Copy the code
SRC/store/Commons/reducer. Js (branch of reducer. Js file)
The reducer of the branch must be a pure function
// Fixed input must have fixed output cannot change previous state cannot return uncertain data (math.random new Date())
// Only internal synchronization can be performed! If the address of the new state is different from the address of the previous state, the new state is considered to be returned (deep copy only works).
import state from "./state"
const reducer = (prevState = state,action) = >{
letnew_state = {... prevState}switch (action.type) {
default:
break;
}
return new_state;
}
export default reducer;
Copy the code
2-3 Components Related configurations
import React,{Component} from "react"
import "./index.scss"
class Template extends Component{
render(){
return (
<div>
Template
</div>)}}export default Template
Copy the code
2-4 Implementation of AXIOS related data request
In order to solve the problem of cross domain, we need to first in the config folder webpackDevServer. Config. Js configuration in the reverse proxy
proxy: {
"/ele": {
target: "http://39.106.171.220:8989".changeOrigin: true}}Copy the code
Axios-utils/post.js encapsulates the Post method
import axios from "axios"
import qs from "querystring"
export default ({url,data})=>{
return axios.post(url,qs.stringify(data))
}
Copy the code
Axios-utils/get.js encapsulates the Get method
import axios from "axios"
export default ({ url, data }) => {
return axios.get(url, {
params:data
})
}
Copy the code
Encapsulates the axios-utils/index.js file
import Post from "./Post"
import Get from "./Get"
import {Component} from "react"
Component.prototype.$get = Get;
Component.prototype.$post = Post;
export { // Easy follow-up actionCreators to use the app should go directly to this.$POST or this.$get
Get,Post
}
Copy the code
The app.js file tests the component through this.$POST
Or this.$get implements the request
componentDidMount(){
// this.$http.get() Component.prototype.$post = axios;
this.$get({
url:"/ele/order/order".data: {limit:3
}
}).then(res= >{
console.log(res)
})
}
Copy the code
2-5 CONFIGURATION of REM
Encapsulate a rem.js file in modules folder to achieve mobile responsive layout.
document.documentElement.style.fontSize =
document.documentElement.clientWidth / 3.75 +"px";
window.onresize = function(){
document.documentElement.style.fontSize =
document.documentElement.clientWidth / 3.75 + "px";
}
Copy the code
3. React-router-dom implements level-1 routing
Install the react – 3-1 the router – the dom
cnpm i react-router-dom -S
Copy the code
3-2 Wrap App components with Router
In index.js, the Router will be wrapped and the HashRouter will be introduced, followed by a “#” at the end of the address
import {HashRouter as Router} from "react-router-dom"
ReactDOM.render(
<Router>
<App />
</Router>.document.getElementById('root'));
Copy the code
3-3 Create a page
Each page is created in SRC/Components /pages and finally exported in index.js.
import Home from './Home'
import Find from './Find'
import Order from './Order'
import Mine from './Mine'
export {
Home,Find,Order,Mine
}
Copy the code
3-4 The app. js file creates a level-1 route
To switch routes, import routes
import {Route} from "react-router-dom"
Copy the code
Path specifies the path, and Component specifies the component to render
render(){
return(
<div>
<Route path="/" component={Home} />
<Route path="/find" component={Find} />
<Route path="/order" component={Order} />
<Route path="/mine" component={Mine} />
</div>)}Copy the code
However, if you enter /list in the browser, both Home and list will match. You need to set exact in /. After setting exact, you can use it only when the exact match is set.
<Route path = "/" component = {Home} exact/>
Copy the code
Render can pass in a function that returns a component after a logical judgment.
<Route path = "/find/a" render = {() = >(<div>Hello, I am/find/a</div>)} / >Copy the code
In Switch, only one route is rendered, which effectively prevents the sibling routes from being rendered multiple times (always render the first component that matches, in order from top to bottom).
The introduction of the Switch
import {Route,Switch} from "react-router-dom"
Copy the code
The first “/” must be followed by exact, because the Switch only renders one route. If exact is not added, every address will be matched with “/” first, so no matter what address is entered, only the Home page will be displayed.
render(){
return(
<Switch>
<Route path = "/" component = {Home} exact/>{/ *<Route path = "/find/a" render = {()= >(<div>Hello, I am/find/a</div>)} / > * /}<Route path = "/find" component = {Find} />
<Route path = "/order" component = {Order} />
<Route path = "/mine" component = {Mine} />
</Switch>)}Copy the code
Mount the default properties in app. defaultProps
App.defaultProps = {
navs:[
{ id: 1.path: "/".component: Home, exact: true },
{ id: 2.path: "/Order".component: Order, exact: false },
{ id: 3.path: "/Find".component: Find, exact: false},
{ id: 4.path: "/mine".component: Mine, exact: false}}]Copy the code
Modify the contents of the Switch
import React,{Component} from 'react';
import {
Home, Order, Find, Mine
} from "./components/pages"
import {Route,Switch} from "react-router-dom"// Import the route object
class App extends Component{
render(){
return (
<div>
<Switch>
<Route path="/" component={Home} exact={true}/>
<Route path = "/find" component = {Find} />
<Route path = "/order" component = {Order} />
<Route path = "/mine" component = {Mine} />
</Switch>
</div>)}}export default App;
Copy the code
3-5 Loop render default properties
You can put a level 1 route on top of App. DefaultProps for loop rendering
Write a method called renderNavs that returns the contents of the Switch
renderNavs(){
return(
<Switch>
<Route path="/" component={Home} exact={true} />{/ *<Route path = "/find/a" render = {()= >(<div>Hello, I am/find/a</div>)} / > * /}<Route path = "/find" component = {Find} />
<Route path = "/order" component = {Order} />
<Route path = "/mine" component = {Mine} />
</Switch>)}Copy the code
Render navs returns the same result as before.
render(){
return(
<div>
{this.renderNavs()}
</div>)}Copy the code
Deconstruct the navs from this.props and loop through the Switch
import React,{Component} from 'react';
import {
Home, Order, Find, Mine
} from "./components/pages"
import {Route,Switch} from "react-router-dom"// Import the route object
class App extends Component{
renderNavs(){
let {navs} = this.props;
return (
<Switch>
{
navs.map(item=>{
return (
<Route key={item.id} path={item.path} component={item.component} exact={item.exact} />)})} {/ *<Route path="/" component={Home} exact={true}/>
<Route path = "/find" component = {Find} />
<Route path = "/order" component = {Order} />
<Route path = "/mine" component = {Mine} />* /}</Switch>)}render(){
return (
<div>
{this.renderNavs()}
</div>
)
}
}
App.defaultProps = {
navs:[
{ id: 1.path: "/".component: Home, exact: true },
{ id: 2.path: "/Order".component: Order, exact: false },
{ id: 3.path: "/Find".component: Find, exact: false},
{ id: 4.path: "/mine".component: Mine, exact: false}}]export default App;
Copy the code
I found that if I visit/directly, the page will not show any content
The reason is that the home component will appear and the render function will execute when the path is /. So App. Js file componentWillReceiveProps hook function will replace path/to/home:
componentWillReceiveProps(props) {
let { pathname } = props.location;
let { replace } = props.history;
if (pathname === "/find") {
replace("/find/coin")
}
if (pathname === "/") {
replace("/home")
}
}
Copy the code
Change /home exact to false:
{ id: 1, path: "/home", component: Home, exact: false }
Copy the code
4. Create AppFooter component
Start styling the AppFooter component by introducing the font-awesome font icon library in index. HTML.
<! -- Introducing font awesome --><link rel="stylesheet" href="%PUBLIC_URL%/font-awesome/css/font-awesome.min.css" />
Copy the code
Write four options in index.js in AppFooter
import React,{Component} from "react"
import "./index.scss"
class AppFooter extends Component{
render(){
return(
<div className = "app-footer">
<a href = "">
<i className = "fa fa-home"></i>
<span>Home page</span>
</a>
<a href="">
<i className="fa fa-podcast"></i>
<span>found</span>
</a>
<a href="">
<i className="fa fa-book"></i>
<span>The order</span>
</a>
<a href="">
<i className="fa fa-user"></i>
<span>my</span>
</a>
</div>)}}export default AppFooter
Copy the code
The React-router provides Link to redirect routes. Import Link first
import{Link} from "react-router-dom"
Copy the code
Then replace the A label with the Link label and add the TO attribute to realize the level-1 route forward
import{Link} from "react-router-dom"
class Template extends Component{
render(){
return(
<div className = "app-footer">
<Link to = "/">
<i className = "fa fa-home"></i>
<span>Home page</span>
</Link>
<Link to = "/find">
<i className="fa fa-podcast"></i>
<span>found</span>
</Link>
<Link to = "/order">
<i className="fa fa-book"></i>
<span>The order</span>
</Link>
<Link to = "/mine">
<i className="fa fa-user"></i>
<span>my</span>
</Link>
</div>)}}export default Template
Copy the code
NavLink can also be used to redirect routes and add ClassName:active (the default) to the tag. We can also use activeClassName to specify the class name of the tag when it is activated. We can set the style of the button when it is selected. You need to add exact match after “/” on the home page.
import React,{Component} from "react"
import "./index.scss"
// import{Link} from "react-router-dom"
import{NavLink} from "react-router-dom"
class Template extends Component{
render(){
return(
<div className = "app-footer">
<NavLink to = "/" exact>
<i className = "fa fa-home"></i>
<span>Home page</span>
</NavLink>
<NavLink to = "/find">
<i className="fa fa-podcast"></i>
<span>found</span>
</NavLink>
<NavLink to = "/order">
<i className="fa fa-book"></i>
<span>The order</span>
</NavLink>
<NavLink to = "/mine">
<i className="fa fa-user"></i>
<span>my</span>
</NavLink>
</div>)}}export default Template
Copy the code
Style active in index. SCSS
&.active{
color:#ae8232;
}
Copy the code
Mount the options to AppFooter. DefaultProps and looping the render to the page.
import React,{Component} from "react"
import "./index.scss"
import {NavLink} from "react-router-dom"
class AppFooter extends Component{
renderFooter(){
let {navs} = this.props;
return (
navs.map(item= >{
return (
<NavLink key={item.id} to={item.path} exact={item.exact}>
<i className={"fa fa-"+item.icon} ></i>
<span>{item.title}</span>
</NavLink>)}}))render(){
return (
<div className="app-footer">
{this.renderFooter()}
</div>
)
}
}
AppFooter.defaultProps = {
navs:[
{id:1.path:"/".icon:"home".exact:true.title:"Home page"},
{id:2.path:"/find".icon:"podcast".exact:false.title:"Discovered"},
{id:3.path:"/order".icon:"book".exact:false.title:"Order"},
{id:4.path:"/mine".icon:"user".exact:false.title:"I"}}]export default AppFooter
Copy the code
5. AppFooter show and hide (two ways)
5-1 is introduced on pages that require the AppFooter component
Import the AppFooter component from Home. Js/Find find.js/Order order.js.
import AppFooter from ".. /.. /commons/AppFooter"
class Find extends Component{
render(){
return(
<div>Find
<AppFooter></AppFooter>
</div>)}}export default List
Copy the code
5-2 Into the global app.js file
Introduce AppFooter globally
import AppFooter from "./components/commons/AppFooter"
Copy the code
Render in Render, now all components have AppFooter
render(){
return(
<div>
{this.renderNavs()}
<AppFooter></AppFooter>
</div>)}Copy the code
Compare this in the Mine component with the App component:
Print in the render of the app.js component
render(){
console.log("App.js".this)
return(
<div>
{this.renderNavs()}
<AppFooter></AppFooter>
</div>)}Copy the code
The app component props will only have navs, which is the default state we mounted it to, and render will only execute once.
Print in the render of the mine.js component
class Mine extends Component{
render(){
console.log("mine.js".this)
return(
<div>Mine</div>)}}Copy the code
The mine component props has history/location/match on it.
(The mine component is wrapped by Route, and the attributes above are the attributes passed to it by Route)
When routing change, mime components on the properties of will change, triggering the componentWillReceiveProps this hook function.
The APP component is not a routing component and cannot listen for route changes. So we want the APP component to listen for route changes. How do we do that?
We can let the APP components become routing components, so it can listen to the routing changed, once the routing changed, APP component componentWillReceiveProps this hook function will also be carried out.
WithRouter is a higher-order component. After the withRouter is wrapped, the APP component will become a pseudo-routing component, which can listen to the changes of the route, but cannot realize the jump. (The mine component can jump/listen for route changes)
The withRouter component is introduced first
import {Route,Switch,withRouter} from "react-router-dom"
Copy the code
Wrap the App with withRouter
export default withRouter(App);
Copy the code
The app.js component is a normal component with navs attributes, but withRouter(App) contains location/match/history attributes. Once the above properties will change, change, App component componentWillReceiveProps this hook will be triggered, once a trigger can within this hook function for the corresponding routing operation of the business logic.
A higher-order component is essentially a function that takes a component and then returns a new component. The previously accepted component will have some additional properties to use.
So let’s just tell it to print the pathname, so we can get the pathname for each route.
componentWillReceiveProps(props){
let pathname = props.location.pathname;
console.log(pathname)
}
Copy the code
We set a default state hasFooter to control whether Footer is shown or hidden. The default value is true.
constructor(){
super(a)this.state = {
hasFooter:true}}Copy the code
Assign false when pathName is “/mine”
componentWillReceiveProps(props){
let pathname = props.location.pathname;
// console.log(pathname)
if(pathname === "/mine") {this.setState({
hasFooter:false}}})Copy the code
Use the hasFooter value in render to control the display and hide of the AppFooter component
render(){
let{hasFooter} = this.state;
// console.log("App.js",this)
return(
<div>{this.renderNavs()} {! hasFooter ||<AppFooter />}
</div>)}Copy the code
In this case, the AppFooter component can be hidden when clicking to refer to the mine page, but it is still hidden when returning to other pages. We need to add else statements to reassign hasFooter to true for non-mine pages
componentWillReceiveProps(props){
let pathname = props.location.pathname;
// console.log(pathname)
if(pathname === "/mine") {this.setState({
hasFooter:false})}else{
this.setState({
hasFooter:true}}})Copy the code
5-3 Appfooter placed
At first we were through componentWillReceiveProps this hook function change state to control the display of components and hidden, it will only be triggered when state changes, but does not execute initialization. So when I jump to mine from another page, the AppFooter component will be hidden, but when I visit mine directly, the AppFooter component will still be displayed. Considering that the render function of the app.js file executes both when initialization and when routing changes, there is no need for state control to show and hide the AppFooter.
renderFooter(){
let {pathname} = this.props.location;
if(pathname==="/mine") return "";
return <AppFooter/>
}
render(){
return (
<div>
{this.renderNavs()}
{this.renderFooter()}
</div>)}Copy the code
6.Mine Secondary route
6-1 Mine Two submodules user and login are created below
Configure a secondary Route in index.js of main, import Route from react-router-dom, and import Login and User submodules.
import React,{Component} from "react"
import "./index.scss"
import {Route} from "react-router-dom"
import Login from "./Login"
import User from "./User"
class Mine extends Component{
render(){
return (
<div>
<Route path="/mine/login" component={Login}/>
<Route path="/mine/user" component={User}/>
Mine
</div>
)
}
}
export default Mine
Copy the code
6-2 A data control is required to display the login or user component
1) Write a userInfo file in store/ Commons /state. Default value is null
export default {
userInfo:null
}
Copy the code
2) Considering that the component needs to use the data in REdux, it needs to encapsulate a group. Encapsulate a connect()
import {connect} from "react-redux" import {bindActionCreators} from "redux" import actionCreators from ".. /.. /store/commons/actionCreators" export default connect(state=>state.commons,dispatch=>{ return bindActionCreators(actionCreators,dispatch) });Copy the code
3) The mine component wants to obtain userInfo, which is managed by Redux, in the form of container nested UI. So the mine component can get userInfo through props.
But the discovery will report an error! The reason is because there are no nested Provider components in the outermost layer! Because the Provider can give it that data, give it that data, wrap it with connect (), get that data, bring it into store, add store property to Provider.
The outermost index.js file:
import {Provider} from "react-redux" import store from "./store" ReactDOM.render( <Provider store={store}> <Router> <App /> </Router> </Provider>, document.getElementById('root'));Copy the code
Above Mine component:
import {CommonsGroup} from ".. /.. /.. /modules/group" //Mine is a routing component that can listen for routing changes //Mine is a UI component, Class Mine extends Component{render(){console.log(" Mine ",this.props) //this.porps return ( <div> <Route path="/mine/login" component={Login}/> <Route path="/mine/user" component={User}/> Mine </div> ) } } export default CommonsGroup(Mine)Copy the code
Conclusion:
Once the Mine component is wrapped by commonsGroup(Mine), the Mine component becomes rich.
The Mine component acts as two roles. A character is itself a routing components, can listen to the change of the routing, routing has changed, once componentWillReceiveProps this hook function will be executed.
Another role is in the form of container nested UI components, where the Mine component can retrieve some of the state managed by Redux and how to change the state. Once the userInfo this state is changed, container components will be listening to the change of the state, and then to the UI components (mime components) passed new property, the property change, mime components componentWillReceiveProps this hook function will be executed.
6-3 Immutable object
Consider that Reducer is a pure function, and prevState cannot be changed randomly. Even if the change is made, a deep copy of a new object must be made, so keep in mind at all times. The immutable library is used to solve this problem.
cnpm i immutable redux-immutable -S
Copy the code
Import fromJS from immutable and wrap state in store/ Commons /reducer.js
// ImMUTABLE library import state from "./state" import {fromJS} from "immutable" const reducer = (prevState = fromJS(state),action)=>{ switch (action.type) { default: return prevState; / / immutable object! } } export default reducer;Copy the code
You’ll notice something’s wrong! Connect (mapStateToProps) needs to be changed
Modules/group/Commons – group. The js file
import {connect} from "react-redux" import {bindActionCreators} from "redux" import actionCreators from ".. /.. /store/commons/actionCreators" export default connect(state=>{ return { userInfo:state.commons.get("userInfo") // Fetch data from immutable via get}},dispatch=>{return bindActionCreators(actionCreators,dispatch)});Copy the code
The Mine component will then retrieve the userInfo data
State.com mons became IMmutable, so we should also make state objects immutable.
We’ll install another tool:
cnpm i redux-immutable -S
Copy the code
In the compiled reducer. Js file, change redux to redux-immutable
// This is a reducer import {combineReducers} from "redux-immutable" import Commons from "./ Commons /reducer" const reducer = combineReducers({ commons }) export default reducer;Copy the code
State is now an immutable object, and you can call the getIn method in immutable to fetch data
import {connect} from "react-redux" import {bindActionCreators} from "redux" import actionCreators from ".. /.. /store/commons/actionCreators" export default connect(state=>{ return { userInfo:state.getIn(["commons","userInfo"]) // State is now immutable}},dispatch=>{return bindActionCreators(actionCreators,dispatch)});Copy the code
6-4 The mine component obtains userInfo to realize the redirect
Since the mine component is wrapped by Route, there will be history attribute and location attribute on it. Push and replace in history will realize Route jump. If we jump from Order to mine and then enter user, the return should return to Order. The process from mine to user in the middle is not recorded, so we need to use replace, and push will record the process, click back, and return to the mine page.
componentDidMount(){ this.checkUserInfo() } //order -- mine (replace) mine/user checkUserInfo(){ let {userInfo,history} = this.props; Replace ("/mine/user")}else{history.replace("/mine/login")}}Copy the code
6-5 Login page Provides the login function
The Mine/Login component also needs to include CommonGroup, wrap the UI component, and then get the user information.
import React,{Component} from "react" import "./index.scss" import {CommonsGroup} from ".. /.. /.. /.. /modules/group" class Login extends Component{constructor(){super() this.login = this.login.bind(this)} Login (){// Login This.props. Login (){return (<div> <button onClick={this.login}> login </button> </div>)}} export default CommonsGroup(Login)Copy the code
To change the Redux status, go to actionCreators
import {CHECK_USER_INFO} from "./const" export default { login(){ return dispatch=>{ setTimeout(() => { let action = { Type: CHECK_USER_INFO, userInfo:{username:" "}} dispatch(action)}, 1000); }}}Copy the code
Commons/reducer. Js to deal with the action
// the prevstate. set method does nothing to change the previous state
// The set method of the immutbale object returns a new object by combining the set value with the previous immutable object
// Without changing the previous prevState object.
import {CHECK_USER_INFO} from "./const"
import state from "./state"
import {fromJS} from "immutable"
const reducer = (prevState = fromJS(state),action) = >{ //
switch (action.type) {
case CHECK_USER_INFO:
return prevState.set("userInfo",action.userInfo)
default:
return prevState; / / immutable object!}}export default reducer;
Copy the code
Then you can click the button and get the user data one second later
Suppose the Login component passes user information to the Action
login(){ this.props.login({ username:"123", password:"456", Success: data = > {alert (data) / / jump to personal center this. Props. The history, the replace ("/mine/user ")}, fail: err = > {alert (err)}})}Copy the code
The action then logs in after identifying the user information
import {CHECK_USER_INFO} from "./const" export default { login({username,password,success,fail}){ return dispatch=>{ setTimeout(() => { if(username==="123" && password==="456"){ let action = { type: CHECK_USER_INFO, userInfo: {username: "@dispatch"}} dispatch(action) ) return false; } fail(" Login failed!" )}, 1000); }}}Copy the code
If the current page does not pass the fail value, the system will report an error if the login fails, saying fail is not defined. In order to enhance the robustness of the code, we can make an optimization. When success or fail is true, the subsequent operation is performed.
setTimeout(()=>{ if(username === "123" && password === "456"){ let action = { type: CHECK_USER_INFO, userInfo: {username: ""}} dispatch(action) success && success(" " ") return false; } fail && fail(" Login failed!" )}, 1000)Copy the code
After logging in successfully to enter the personal center interface, first also need to use CommonsGroup package, so that you can get the data.
import React,{Component} from "react" import "./index.scss" import {CommonsGroup} from ".. /.. /.. /.. /modules/group" class User extends Component{render(){return(<div> <p> {this.props.userInfo.username}</p> </div> ) } } export default CommonsGroup(User)Copy the code
If the /mine/user page fails to retrieve data from null, we can add a check to this. Props. UserInfo if true.
< p > user name: {this. Props. The userInfo && enclosing props. The userInfo. Username} < / p >Copy the code
Click the button to exit the function.
import React,{Component} from "react" import "./index.scss" import {CommonsGroup} from ".. /.. /.. /.. /modules/group" class User extends Component{ constructor(){ super() this.exit = this.exit.bind(this) } exit(){ this.props.exit(); / / change the status of the inside story this. Props. History. The replace ("/mine/login ")} / / jump to the login interface render () {return (< div > < p > < button </button></p> <p> {this.props.userInfo&&this.props.userInfo.username}</p> </div> ) } } export default CommonsGroup(User)Copy the code
In the store/Commons/actionCreators. Js add exit way
exit(){
let action = {
type: CHECK_USER_INFO,
userInfo:null
}
return action;
}
Copy the code
Consider:
When the login is successful, the jump function is realized in the success callback function of login, and then click exit in the personal center interface to realize the exit function. Considering that both the login and personal center components are in Mine component, can Mine component realize these two jumps?
The Mine component acts as two roles, a routing &UI component.
The userInfo changed, equivalent to a state change, mime component componentWillReceiveProps hook will be executed.
Routing, when there is a change/mine/login = = > / mine/user routing has changed, and mime components componentWillReceiveProps hook will be executed.
So in mime component componentWillReceiveProps hook function, according to the state to achieve the function of the Login and the User to jump.
componentWillReceiveProps(nextProps){ if(nextProps.userInfo ! == this.props. UserInfo){this.props (props)}} checkUserInfo(props){// Redirect the method let {userInfo,history} = props || this.props; Replace ("/mine/user")}else{history.replace("/mine/login")}}Copy the code
When we click mine again on /mine/user page, we will jump to /mine page, at this time the route changes, we need to add another judgment to the if statement, execute the following statement if the previous statement is false, Let the this.checkUserInfo(nextProps) method be executed in both cases.
if(nextProps.userInfo ! == this.props.userInfo || nextProps.location.pathname==="/mine")Copy the code
6-6 Import antD-Mobile component library
cnpm install antd-mobile --save
cnpm install babel-plugin-import -D
Copy the code
The webpack.config.js file searches for babel-loader
plugins: [
......
["import", { libraryName: "antd-mobile", style: "css" }]
],
Copy the code
6-7 Login registration page
Start by introducing the NavBar component from the ANTD-Mobile component library
import { NavBar, Icon } from 'antd-mobile';
Copy the code
Render the NavBar in the page
render(){
return(
<div>
<NavBar
mode="light"
icon={<Icon type="left" />}
onLeftClick={() => this.props.history.replace("/")}
rightContent={[
<Icon key="1" type="ellipsis" />,
]}
>NavBar</NavBar>
</div>
)
}
Copy the code
Pull out a component loginTextForm.js for mobile login
import React,{Component} from "react" class LoginTextForm extends Component{ render(){ return( <form> <div className = "Form-group "> <input type="text" placeholder =" placeholder "/> </div> <div className="form-group"> <input type="text" placeholder Placeholder =" /> </div> <button> </button> </form>)}} export default LoginTextForm;Copy the code
Introduced in index.js
import LoginTextForm from "./LoginTextForm"
Copy the code
Style the loginTextForm.js component and add a P tag on the front page to switch forms with one click.
<p className = "change-type"> </p>Copy the code
Encapsulate the rendered content as a separate function changeLoginType()
ChangeLoginType (){return(<div className="content"> <LoginTextForm /> <p className="change-type"> </p> </div>)}Copy the code
Add a click event changeType to the p TAB. If we want to switch the p TAB form, we need to rerender the page and define a default state.
constructor(){ super() this.login = this.login.bind(this) this.state = { loginType:"text"; }}Copy the code
When the P tag is clicked, the changeType method is called, the state changes, and the render function is re-executed.
let {loginType} = this.state;
const changeType = () =>{
this.setState({
loginType:"user"
})
}
Copy the code
ChangeLoginType () is called again, executes the if statement, evaluates, reassigns the Form to LoginUserForm, and rerenders it on the page.
if(loginType ! == "text"){ Form = LoginUserForm }Copy the code
Assign a separate value to the title, which changes when toggled.
Let title = "login" if(loginType! <p onClick = {changeType} className="change-type">{title}</p>Copy the code
The title bar of NavBar has also changed
{this.state.loginType === "text" ? "SMS quick" : "Account password "} loginCopy the code
Now we can switch components by clicking on labels, but we can’t switch them back again, so we define a separate type as user. When we click on the P tag, execute the changeType method, assign type to loginType to change the state, and then determine when loginType! When == “text”, reassign type back to “text”, thus achieving the effect of clicking on the P TAB to switch back and forth between the two forms.
changeLoginType(){ let Form = LoginTextForm; let {loginType} = this.state; Let type = "user" if(loginType! == "text"){Form = LoginUserForm title = "text" type = "text"} const changeType = () =>{this.setState({this. loginType:type }) } return( <div className="content"> <Form /> <p onClick = {changeType} className="change-type">{title}</p> </div> ) }Copy the code
Effect demonstration:
6-8 Login function
Add type = “submit” to the button tag in Login’s index.js and onSubmit to the form form
Return (<form onSubmit = {this.handleSubmit}> <div className = "form-group"> <input type = "text" placeholder = "placeholder "/> </div> <div className="form-group"> <input type="text" placeholder=" validation code "/> </div> <button type=" submit" className= </button> </form>Copy the code
Add the loginByText and loginByUser methods to the actionCreators
loginByText({ phone, code, success, fail }) { return dispatch => { setTimeout(() => { if (phone === "110" && code === "456") { let action = { type: CHECK_USER_INFO, userInfo: {username: "jack"}} dispatch(action) SUCCESS && Success (" mobile login succeeded! ") return false; } fail && Fail (" Mobile login failed!" ) }, 1000) } }, loginByUser({ username, password, success, fail }) { return dispatch => { setTimeout(() => { if (username === "123" && password === "456") { let action = { type: CHECK_USER_INFO, userInfo: {username: "jack"}} dispatch(action) SUCCESS && Success (" user login succeeded! ") return false; } fail && fail(" User login failed!" )}, 1000)}}Copy the code
Introduce CommonsGroup in loginTextForm. js and wrap the LoginTextForm so that the LoginTextForm gets the actionCreators method defined in this.props. LoginByText.
We use an uncontrolled component approach to get data
<div className = "form-group"> <input ref = {el => this.phone = el} type = "text" placeholder = "placeholder "/> </div> <div ClassName ="form-group"> <input ref={el => this.code = el} type="text" placeholder=" placeholder "/> </div>Copy the code
Write the handleSubmit method in LoginTextForm and pass the parameters to actionCreators
handleSubmit = () => {
this.props.loginByText({
phone:this.phone.value,
code:this.code.value,
success:data=>{
alert(data)
},
fail:err=>{
alert(err)
}
})
}
Copy the code
Make another copy and change it to loginUserform.js
import React, { Component } from "react" import { CommonsGroup } from ".. /.. /.. /.. /modules/group" class LoginUserForm extends Component { handleSubmit = () => { this.props.loginByUser({ username: this.username.value, password: this.password.value, success: data => { alert(data) }, fail: err => { alert(err) } }) } render() { return ( <form onSubmit={this.handleSubmit}> <div className="form-group"> <input /> </div> <div className="form-group"> <input ref={el => /> </div> <button type="submit" className="login"> </button> </form>) } } export default CommonsGroup(LoginUserForm);Copy the code
Introduce toasts in the component library
We give a light prompt when the user logs in successfully
success:data=>{
Toast.success(data, 1);
}
Copy the code
Effect demonstration:
If the login fails, the callback function clears the password and gets focus
fail:err=>{
Toast.fail(err, 1, ()=>{
this.code.value=""
this.code.focus()
})
}
Copy the code
Effect demonstration:
Disables the default behavior of the login button
e.preventDefault();
Copy the code
Due to the word limit, I have to divide this article into two chapters. Please read on the React Family APP below. Or head straight to the visual Pie for a more complete reading experience.