回顾
- React(js库)
- 创建组件的方式(函数创建、类的创建)
- 创建方式的区别和联系(函数创建方式没有生命周期,函数创建方式没有state,函数方式在传值的时候接收用的是 props相反类的创建方式接收传值数据的时候 用的是this.props)
- state(setState)
- props
- 组件通信(父子、子父)
未来两周的安排
- react(生命周期、路由、stylus、UI库、状态管理、flux、redux、做项目)
- Angular(2天)
- D3.js数据图表(结合svg)
- echarts和highCharts、百度地图
- git代码版本管理工具
- 工作流程公司人员安排
- 回过头看一下电商的项目(vant的使用,购物车的数据抓取)
一、生命周期
- Vue中的生命周期(8个)
- React中生命周期都有哪些内容
组件一加载,触发4个生命周期,生命周期的执行顺序是
constructor()初始化
componentWillMount() 将要挂载
render() 渲染 的是虚拟dom
componentDidMount() 挂载完成 — 在这个生命周期阶段我们会执行调取接口执行axios或者ajax还有倒计时,周期性函数等等
当数据发生变更,会影响视图的变化,我们会发现生命周期触发了那几个生命周期函数
分两种状态shouldComponentUpdate() 有两个参数,第一个参数是newProps新属性,第二个参数是newState
一、没有shouldComponentUpdate()
componentWillUpdate 组件将要更新
render() 重新渲染DOM结构
componentDidUpdate 更新完成
二、有shouldComponentUpdate()这个生命周期,也分两种状态,你要返回true OR false(根据你自己的需求和逻辑)
返回true的情况:会再次执行componentWillUpdate 组件将要更新
render() 重新渲染DOM结构
componentDidUpdate 更新完成
这三个生命周期函数
返回false的时候,生命周期结束,视图也不会有变化
二、路由
2.1基本路由
- 组件的渲染方式一、引入组件渲染二、通过路由的控制
- 在React中没有独立的路由文件,我们也要通过下载路由插件去实现路由,下载方式
npm install react-router-dom
react-router-dom@5.1.2 - 基本路由的实现
- 创建一个router文件夹,在文件夹下去创建一个index.js文件
//创建了一个函数组件
import React from 'react'
//引入路由的组件方法
import {Switch,Route,Redirect} from 'react-router-dom'
//引入你要渲染的组件
import Home from '../components/pages/home'
import Car from '../components/pages/car'
import Mine from '../components/pages/mine'
function Router(){
return(
<div>
<Switch>
<Route path='/home' component={Home}></Route>
<Route path='/car' component={Car}></Route>
<Route path='/mine' component={Mine}></Route>
<Redirect to='/home'></Redirect>
</Switch>
</div>
)
}
export default Router
- 在你想要渲染的地方去引入这个路由文件
- 我想在app.js中渲染
//路由出口(相当于vue中的router-view)
import Router from './router'
<Router></Router>
- 浏览器无法解析这些路由属性,所以在index.js(主的js文件)中 引入相关解析的路由属性方法
- BrowserRouter这个方法不带# 如果你想使用hash的方法就调用HashRouter
import { BrowserRouter } from 'react-router-dom'
ReactDOM.render(<BrowserRouter><App /></BrowserRouter>, document.getElementById('root'));
import {HashRouter} from 'react-router-dom'
ReactDOM.render(<HashRouter><App /></HashRouter>, document.getElementById('root'));
- 用户看到的路由点击视图
//为用户创建视图 从路由插件中调取链接方法
import {Link,NavLink} from 'react-router-dom'
// 用Link这种链接方式没有activeClassName
// 用NavLink这种链接方式有activeClassName
<NavLink to='/home' activeClassName='select'>首页</NavLink>
<NavLink to='/car' activeClassName='select'>购物车</NavLink>
<NavLink to='/mine' activeClassName='select'>个人中心</NavLink>
- 去掉路由文件, 把路由文件(一级路由)整合到app.js 中 ,出口相当于router-view
import React from 'react';
//先引入路由插件和路线方法
//引入路由跳转链接方法
import {Switch,Route,Redirect} from 'react-router-dom'
//引入 你要渲染的组件
import Index from './components/view/index'
import Login from './components/view/login'
function App() {
return (
<div className="App">
<Switch>
{/* 路由出口 相当于router-view*/}
<Route path='/login' component={Login}></Route>
<Route path='/index' component={Index}></Route>
{/* 重定向 */}
<Redirect to='/login'></Redirect>
</Switch>
</div>
);
}
export default App;
2.2嵌套路由
- 在一级组件内想要渲染二级路由,先引入路由插件,再直接渲染,调用链接方法
//类的创建方式
import React from 'react'
//引入路由插件
import {Switch,Route,Redirect,NavLink} from 'react-router-dom'
//渲染引入二级组件内容
import Home from '../pages/home'
import Car from '../pages/car'
import Mine from '../pages/mine'
class Index extends React.Component {
constructor() {
super()
}
render() {//渲染
return (
<div>
<NavLink to='/index/home' activeClassName='select'>首页</NavLink>
<NavLink to='/index/car' activeClassName='select'>购物车</NavLink>
<NavLink to='/index/mine' activeClassName='select'>个人中心</NavLink>
{/* 路由的内容显示 路由出口 */}
<Switch>
<Route path='/index/home' component={Home}></Route>
<Route path='/index/car' component={Car}></Route>
<Route path='/index/mine' component={Mine}></Route>
<Redirect to='/index/home'></Redirect>
</Switch>
</div>
)
}
}
export default Index
2.3编程式导航
- 想要实现编程式导航
this.props.history.push('path')
this.props.history.replace('path')
this.props.history.go('-1')
以上三个方法都是基于,这个是路由渲染,props中含有history相关的属性和方法
如果这个组件不是路由渲染,是组件嵌套,那么我还想用路由的编程式导航,如何解决
//引入withRouter 这个方法 去使用路由中的属性方法
import { withRouter } from 'react-router-dom'
export default withRouter(goBack)
以上两句代码,让当前的类中props含有了路由的编程式方法 比如history
2.4 动态路由
- app.js
//动态路由
<Route path='/movieDetail/:id' component={MovieDetail}></Route>
- 动态路由的两种方法
- movieLis.js 都是传值
//动态路由的跳转事件
goDetail(id) {
this.props.history.push('/movieDetail/' + id)
}
<ul>
{
movieList.map(item => {
return <li key={item.id}>
{/*
动态路由的第一种方法
<NavLink to={'/movieDetail/'+item.id}><h1>{item.name}</h1></NavLink>
*/}
{/*
动态路由的第二种方法 编程式导航
this.props.history.push('/movieDetail/'+id)
<h1 onClick={this.goDetail.bind(this,item.id)}>{item.name}</h1>
*/}
</li>
})
}
</ul>
- movieDetail取值
<h1>获取动态的路由参数---{this.props.match.params.id}</h1>
2.5React中配置stylus和stylus-loader
{
test: /\.styl$/,
use: [
require.resolve('style-loader'),
require.resolve('css-loader'),
require.resolve('stylus-loader')
]
},
三、美团案例
3.1 创建一个项目
create-react-app mydemo
用react创建一个脚手架项目
3.2 下载路由插件
npm i(install) react-router-dom
3.2 初始化整个项目
删除掉没有用的文件
留下index.js index.css app.js app.css 以及相关的文件
清空index.js 月app.js 文件内无关的内容
3.3 确定当前项目是PC端还是移动端(移动端)
- 创建一个assets文件夹引入js和css文件(rem和重置样式内容)
- index.js
//第一步 引入rem.js
import './assets/js/remScale'
//第二步 引入重置样式
import './assets/css/reset.css'
3.4 创建component文件夹以及相关组件与其他目录结构
- 经过需求分析得知我们有6个一级路由4个二级路由
- 创建10个组件
- 目录结构
src
assets 静态资源管理
js css images
components 组件
pages views
common 公共组件
util 实用工具
axiosConfig配置文件 axios.js 接口文件
3.5 搭建一级路由
- app.js
import React from 'react';
import './App.css';
//引入一级路由
import Login from './components/pages/login'
import Index from './components/pages/index'
import MovieList from './components/pages/movieList'
import MovieDetail from './components/pages/movieDetail'
import FoodList from './components/pages/foodList'
import FoodDetail from './components/pages/foodDetail'
//引入路由插件
import {Switch,Route,Redirect} from 'react-router-dom'
function App() {
return (
<div className="App">
{/* app根组件中 只有一级路由的出口 */}
<Switch>
<Route path='/login' component={Login}></Route>
<Route path='/index' component={Index}></Route>
<Route path='/movieList' component={MovieList}></Route>
<Route path='/movieDetail' component={MovieDetail}></Route>
<Route path='/foodList' component={FoodList}></Route>
<Route path='/foodDetail' component={FoodDetail}></Route>
<Redirect to='/index'></Redirect>
</Switch>
</div>
);
}
export default App;
- index.js主js文件
//引入浏览器解析路由的方法
import {BrowserRouter} from 'react-router-dom'
ReactDOM.render(<BrowserRouter><App /></BrowserRouter>, document.getElementById('root'));
- 去浏览器中手动输入一级路由的地址,测试是否好用
3.6 搭建二级路由
- index.js(一级路由)
import React from 'react'
//引入二级路由的组件
import Home from '../view/home'
import Mine from '../view/mine'
import Car from '../view/car'
import Sort from '../view/sort'
//引入路由插件以及跳转链接
import {Switch,Route,Redirect,NavLink} from 'react-router-dom'
class Index extends React.Component{
render(){
return(
<div>
<Switch>
<Route path='/index/home' component={Home}></Route>
<Route path='/index/sort' component={Sort}></Route>
<Route path='/index/car' component={Car}></Route>
<Route path='/index/mine' component={Mine}></Route>
<Redirect to='/index/home'></Redirect>
</Switch>
<div className='footer'>
<NavLink to='/index/home'>首页</NavLink>
<NavLink to='/index/sort'>分类</NavLink>
<NavLink to='/index/car'>购物车</NavLink>
<NavLink to='/index/mine'>个人中心</NavLink>
</div>
</div>
)
}
}
export default Index
3.7从电影入口跳转到电影列表页面
- home.js
<NavLink to='/movieList'>
<h1 className='box'>电影</h1>
</NavLink>
<h1 className='box' onClick={this.goFoodList.bind(this)}>美食</h1>
3.8封装一个公共的返回按钮组件
- goBack
//公共的返回按钮组件
import React from 'react'
//引入withRouter 这个方法 去使用路由中的属性方法
import { withRouter } from 'react-router-dom'
class goBack extends React.Component{
render(){
console.log(this,'组件')
return(
<div>
<button onClick={()=>this.props.history.go(-1)}>返回</button>
</div>
)
}
}
export default withRouter(goBack)
3.9从电影列表跳转到电影详情(动态路由)
- 动态路由的两种方法
- movieLis.js 都是传值
//动态路由的跳转事件
goDetail(id) {
this.props.history.push('/movieDetail/' + id)
}
<ul>
{
movieList.map(item => {
return <li key={item.id}>
{/*
动态路由的第一种方法
<NavLink to={'/movieDetail/'+item.id}><h1>{item.name}</h1></NavLink>
*/}
{/*
动态路由的第二种方法 编程式导航
this.props.history.push('/movieDetail/'+id)
<h1 onClick={this.goDetail.bind(this,item.id)}>{item.name}</h1>
*/}
</li>
})
}
</ul>
- movieDetail取值
<h1>获取动态的路由参数---{this.props.match.params.id}</h1>
3.10从电影列表跳转到电影详情(query传参的方式)
- 两种方法
- 电影列表的传值
{/* query的第一种方法 */}
<NavLink to={'/movieDetail?id=' + item.id}><h1>{item.name}</h1></NavLink>
{/* 第二章方法是编程式导航 */}
<h1 onClick={this.goDetail.bind(this,item.id)}>{item.name}</h1>
//query的跳转事件
goDetail(id) {
this.props.history.push('/movieDetail/?id=' + id)
}
- 跳转到详情页之后(取值)
//http://localhost:3000/movieDetail?id=1&name=zhangsang
//获取url传递过来的?和query参数
/* // 第一种方法 原生js的方法
let str = this.props.location.search //?id=1&name=zhangsang
//对上述字符串进行截取 slice substr substring
let query = str.slice(1) //id=1&name=zhangsang
//对query 进行转换 把字符串拆分成数组以&
let arr = query.split('&') //["id=1", "name=zhangsang"]
//定义obj空对象
let obj= {}
//针对上述数组进行循环遍历 再去拆分等号
arr.forEach(item=>{
console.log(item,'sei')
//循环拆分'=' ["id", "1"] ["name", "zhangsang"]
let temArr = item.split('=')
obj[temArr[0]]=temArr[1]
})
console.log(obj,'转换之后的对象')// {id: "1", name: "zhangsang"}
this.setState({
query:obj
}) */
//第二种方法 node.js
//截取字符串 截取?后面的内容
let str = this.props.location.search //?后面的内容
let query = str.slice(1)//a=2&id=1
query = querystring.parse(query) //{a: "2", id: "1"}
this.setState({
query
})
3.11从电影列表跳转到电影详情(state传参的方式)
- 两种方法
- 电影列表的传值
{/* url不带任何参数,不是动态路由,就是传递state 第一种方法*/}
<NavLink to={
{
pathname: '/movieDetail',
state: {
id: item.id
}
}
}><h1>{item.name}</h1></NavLink>
{/* 编程式导航 */}
<h1 onClick={this.goDetail.bind(this,item.id)}>{item.name}</h1>
//query的跳转事件
goDetail(id) {
this.props.history.push({
pathname:'/movieDetail',
state:{
id
}
})
}
- 跳转到详情页之后(取值)
<h1>获取state路由参数---{this.props.location.state.id}</h1>
3.12开启一个后台服务器(接口服务)
node app.js
- 如果端口号冲突的话,可以修改
3.13调取接口准备
- 下载axios
npm install(i) axios
- 全局挂载axios
import axios = axios
React.component.prototype.axios = axios
- axios使用方法
axios({
url:'',
method:'get/post',
params:{}//get
data:{}//data
})
.then(res=>{})
.catch(err=>{})
//get
axios.get('地址',{
params:{}
})
.then(res=>{})
.catch(err=>{})
//post
axios.post('地址',data)
.then(res=>{})
.catch(err=>{})
3.14美食列表跳转详情页并传参调取接口
- foodList
import React from 'react'
import '../../assets/css/foodList.css'
//引入返回按钮组件
import GoBack from '../../common/goBack'
class foodList extends React.Component {
constructor() {
super()
this.state = {
foodList: []
}
}
componentDidMount() {//挂载
//页面一加载就调取美食列表接口
this.axios({
url: '/list',
method: 'get',
params: {
}
})
.then(res => {
console.log(res, '成功之后的响应')
if (res.data.code == 200) {
this.setState({
foodList: res.data.data
})
}
})
.catch(err => {
})
}
//跳转到详情页
goDetail(id){
/*动态路由 this.props.history.push('/foodDetail/'+id) */
/* query传参 this.props.history.push('/foodDetail?id='+id) */
this.props.history.push({
pathname:'/foodDetail',
state:{
id
}
})
}
render() {
const { foodList } = this.state
return (
<div className='foodList'>
<h1>我是foodList页面</h1>
{/* <button onClick={()=>this.props.history.go(-1)}>返回</button> */}
<GoBack></GoBack>
<ul>
{
foodList.map(item => {
return <li onClick={this.goDetail.bind(this,item.id)} className='box' key={item.id}>
<p>商品名称:{item.name}</p>
<p>商品价格:{item.price}</p>
</li>
})
}
</ul>
</div>
)
}
}
export default foodList
- foodDetail
import React from 'react'
import querystring from 'querystring'
//引入返回按钮组件
import GoBack from '../../common/goBack'
class foodDetail extends React.Component {
constructor() {
super()
this.state = {
info: ''
}
}
componentDidMount() {
//转换query参数的方法
let str = this.props.location.search
let query = str.slice(1)
query = querystring.parse(query)
//组件一加载 调取详情接口
this.axios.get('/detail', {
params: {
//id: this.props.match.params.id 动态路由取值
//id:query.id query的参数
id:this.props.location.state.id
}
})
.then(res => {
console.log(res, '详情页面响应')
if (res.data.code === 200) {
this.setState({
info: res.data.data
})
}
})
.catch(err => {
})
}
render() {
const { info } = this.state
return (
<div>
<h1>我是foodDetail页面</h1>
<GoBack></GoBack>
<div>
<p>商品名称:{info.name}</p>
<p>商品价格:{info.price}</p>
<p>商品详情:{info.info}</p>
</div>
</div>
)
}
}
export default foodDetail
3.15 在react中解决跨域问题
- package.json
"proxy":"http://localhost:3000" //http://localhost:3000这个是你要跨域的目标地址
- 解决完跨域问题一定要记得重启
3.16 全局封装接口和axios拦截器
- 不需要在组件原型挂载axios,对于接口统一管理
- axiosConfig
//引入axios
import axios from 'axios'
const http = axios.create()
//请求拦截
http.interceptors.request.use(config=>{
return config
})
//响应拦截
http.interceptors.response.use(res=>{
console.log(res,'全局响应')
return res.data
})
export default http
- axios
//调取axios的配置文件
import http from './axiosConfig'
const getData= {}
//美食列表
getData.foodList = ()=>{
return http.get('/list')
}
//美食详情
getData.foodDetail = (data)=>{
return http.get('/detail',{
params:data
})
}
export default getData
3.16 Login登录页面(未调取接口的状态)
*login.js
import React from 'react'
//引入alert组件
import Alert from '../../common/alert'
class Login extends React.Component {
constructor() {
super()
this.state = {
userInfo: {
name: '',
pass: ''
},
msg: ''
}
}
//封装一个input输入变化的事件
changeInfo(e, type) {
this.setState({
userInfo: {
...this.state.userInfo,
[type]: e.target.value
}
})
}
//登录事件
login() {
console.log(this.state.userInfo)
const { userInfo } = this.state
//登录的逻辑判断
//判空
if (userInfo.name == '' || userInfo.pass == '') {
// alert('用户名或者密码不能为空')
this.setState({
msg: '用户名或者密码不能为空'
})
return
}
//用户名密码验证
if (userInfo.name == 'admin' && userInfo.pass == '123') {
//登录成功
// alert('登录成功')
this.setState({
msg: '登录成功'
})
this.props.history.push('/index')
} else {
//登录失败
// alert('用户名或者密码错误')
this.setState({
msg: '用户名或者密码错误'
})
}
}
//封装一个让alert消失的函数
getShow(){
this.setState({
msg:''
})
}
render() {
const { userInfo,msg } = this.state
return (
<div>
<h1>我是登录页面</h1>
<hr />
<div>
请输入用户名:<input type="text" defaultValue={userInfo.name} onChange={(event) => { this.changeInfo(event, 'name') }} />
</div>
<div>
请输入密码:<input type="text" defaultValue={userInfo.pass} onChange={(event) => { this.changeInfo(event, 'pass') }} />
</div>
<button onClick={this.login.bind(this)}>登录</button>
{msg?<Alert msg={msg} getShow={this.getShow.bind(this)}></Alert>:''}
</div>
)
}
}
export default Login
3.17 Alert弹框页面(全局的弹框信息)
- alert.js
//公共的提示框
import React from 'react'
//引入样式
import '../assets/css/alert.css'
class Alert extends React.Component{
constructor(){
super()
// this.state={
// isShow:true
// }
}
//设定一个计时器 时间到了 让弹框消失
componentDidMount(){//挂载
this.timer= setTimeout(()=>{
// this.setState({
// isShow:false
// })
this.props.getShow()
},2000)
}
componentWillUnmount(){//将要销毁
//清除定时器
clearTimeout(this.timer)
}
render(){
return(
<div className='alert'>
{/* {this.state.isShow ? <div className='box'>{this.props.msg}</div> : ''} */}
<div className='box'>{this.props.msg}</div>
</div>
)
}
}
export default Alert
3.18 post方法提交参数与后端需求不一致(它的需求是字符串)
- post方法提交参数与后端需求不一致(它的需求是字符串),而我们是对象形式,一直报参数为空,可是参数又有内容,那么需要转化下提交的形式,借助于qs方法。
- qs的下载命令
npm install(i) qs
- 接口配置
//引入qs转换插件 (有时候后端需要的是字符串形式的提交)
import qs from 'qs'
//登录接口
getData.login = (data)=>{
//stringify 把对象转化成字符串
return http.post('/api/form2.php',qs.stringify(data))
}
- Login.js (调取接口的文件)
import React from 'react'
//引入接口
import api from '../../util/axios'
//引入alert组件
import Alert from '../../common/alert'
class Login extends React.Component {
constructor() {
super()
this.state = {
userInfo: {
user: '',
pass: ''
},
msg: ''
}
}
//封装一个input输入变化的事件
changeInfo(e, type) {
this.setState({
userInfo: {
...this.state.userInfo,
[type]: e.target.value
}
})
}
//登录事件
login() {
console.log(this.state.userInfo)
const { userInfo } = this.state
//登录的逻辑判断
//判空
if (userInfo.user == '' || userInfo.pass == '') {
// alert('用户名或者密码不能为空')
this.setState({
msg: '用户名或者密码不能为空'
})
return
}
//用户名密码验证
//调取登录接口
api.login(userInfo)
.then(res => {
console.log(res, '成功之后的响应')
this.setState({
msg: res.data
})
})
.catch(err => {
})
}
//封装一个让alert消失的函数
getShow() {
this.setState({
msg: ''
})
}
render() {
const { userInfo, msg } = this.state
return (
<div>
<h1>我是登录页面</h1>
<hr />
<div>
请输入用户名:<input type="text" defaultValue={userInfo.user} onChange={(event) => { this.changeInfo(event, 'user') }} />
</div>
<div>
请输入密码:<input type="text" defaultValue={userInfo.pass} onChange={(event) => { this.changeInfo(event, 'pass') }} />
</div>
<button onClick={this.login.bind(this)}>登录</button>
{msg ? <Alert msg={msg} getShow={this.getShow.bind(this)}></Alert> : ''}
</div>
)
}
}
export default Login
3.19 React中使用stylus
- 下载stylus以及相关的加载器
npm install(i) stylus stylus-loader
- 暴露出配置文件
npm run eject
重启项目 - 进行stylus加载器的配置
config=>webpack.config.js=>找到’oneOf’,在里面进行配置 重启项目
{
test: /\.styl$/,
use: [
require.resolve('style-loader'),
require.resolve('css-loader'),
require.resolve('stylus-loader')
]
},
- 更改了一个alert.css => alert.styl
.alert
.box
width 55%
height 0.4rem
position absolute
left 0
bottom 0
right 0
top 0
line-height 0.4rem
margin auto
background: #333333
color #fff
4、UI样式库基于React
4.1 Ant Design of React(PC端)
-
官网地址
https://ant.design/index-cn
-
特性
✨ 特性#
🌈 提炼自企业级中后台产品的交互语言和视觉风格。
📦 开箱即用的高质量 React 组件。
🛡 使用 TypeScript 开发,提供完整的类型定义文件。
⚙️ 全链路开发和设计工具体系。
🌍 数十个国际化语言支持。
🎨 深入每个细节的主题定制能力。
- 环境
支持环境#
现代浏览器和 IE11 及以上(需要 polyfills)。
支持服务端渲染。
Electron(桌面应用)
- 安装
npm install(i) antd
- 全局引入antd的css文件
//全局引入antd css样式
import 'antd/dist/antd.css'