Swiper
- 首先安装并引入swiper,同时引入css文档
- yarn add swiper
- 按照文档引入
- 动态数据请求
- 注:当页面中有多个轮播图时,要起一个id名来避免错误
项目流程简单记录
1. 创建项目,在项目中创建基本的目录如:
- components
- layout
- utils
- pages/views
2. 安装sass/less(具体查看sass安装)
- cnpm i node-sass sass-loader -D
3.基本布局
- 头部、中间、底部
- 头部和底部为公共性组件–components
- 中间–pages
- 以上都在LayOut中引入形成布局
- layout在App.js中引入
4.移动端自适应问题:参照具体文档(流行rem+弹性盒)
-
淘宝:常用
- https://github.com/amfe/lib-flexible
/* 通过js来动态添加rem */ ;(function(designWidth, maxWidth) { var doc = document, win = window, docEl = doc.documentElement, remStyle = document.createElement("style"), tid; function refreshRem() { var width = docEl.getBoundingClientRect().width; maxWidth = maxWidth || 540; width>maxWidth && (width=maxWidth); var rem = width * 100 / designWidth; remStyle.innerHTML = 'html{font-size:' + rem + 'px;}'; } if (docEl.firstElementChild) { docEl.firstElementChild.appendChild(remStyle); } else { var wrap = doc.createElement("div"); wrap.appendChild(remStyle); doc.write(wrap.innerHTML); wrap = null; } //要等 wiewport 设置好后才能执行 refreshRem,不然 refreshRem 会执行2次; refreshRem(); win.addEventListener("resize", function() { clearTimeout(tid); //防止执行两次 tid = setTimeout(refreshRem, 300); }, false); win.addEventListener("pageshow", function(e) { if (e.persisted) { // 浏览器后退的时候重新计算 clearTimeout(tid); tid = setTimeout(refreshRem, 300); } }, false); if (doc.readyState === "complete") { doc.body.style.fontSize = "16px"; } else { doc.addEventListener("DOMContentLoaded", function(e) { doc.body.style.fontSize = "16px"; }, false); } })(375, 750); // 备注: 这里的375本身就应该写成750 ,但是写成750之后,我们设计稿的尺寸要/50,不好算,我就想,除以100更好算,所以我改成了375
-
网易:面试
function font () { document.documentElement.style.fontSize = document.documentElement.clientWidth / 3.75 + 'px' } font() window.onresize = font
-
阿里的一个项目
(function(doc, win) { const docEl = doc.documentElement, resizeEvt = 'orientationchange' in window ? 'orientationchange' : 'resize', recalc = function() { const clientWidth = docEl.clientWidth; if (!clientWidth) return; let max = 24; let min = 9.3125; let size = 20 * (clientWidth / 320); size = Math.min(size, max); size = Math.max(size, min); docEl.style.fontSize = size + 'px'; console.log(docEl.style.fontSize, 'em= =====') }; if (!doc.addEventListener) return; win.addEventListener(resizeEvt, recalc, false); doc.addEventListener('DOMContentLoaded', recalc, false); })(document, window);
5. sass(复习)
- 注意: sass文件之间引用使用@import , 在路径上还得加一个~
@import ‘~/…/…/…/assets/global_style/theme.scss’;
6. 以头部为例,配置自适应,写主题颜色
- 自适应文档(utils- rem.js)在全局引入,如index.js、App.js等
- 主题的scss在assets文件夹下的global_style中供整个项目使用
7.头部、底部、中间、高度要设为百分之百,并且引入全局reset.css
示例:
html,body,.App,#root{
height: 100%;
}
//home页面将布局撑开:sass
.home-box{
flex: 1;
overflow: hidden;
}
8.打造底部组件( 局部 )
-
写好结构
-
渲染数据
-
修改样式:
-
弹性盒
-
注意a标签应该有激活颜色:写法->&.active{}
-
移动端布局是尽量不要使用继承,上线是会出错
-
//index.js
import React,{useState} from 'react'
import './index.scss'
const TabBar = props => {
const [ tanBars ] = useState([
{
id: 1,
iconName: 'fa-home',//icon类名
text: '首页',
path: ''//路由路径
},
{
id: 2,
iconName: 'fa-reorder',//icon类名
text: '分类',
path: ''//路由路径
},
{
id: 3,
iconName: 'fa-square',//icon类名
text: '9.9包邮',
path: ''//路由路径
},
{
id: 4,
iconName: 'fa-shopping-cart',//icon类名
text: '购物车',
path: ''//路由路径
},
{
id: 5,
iconName: 'fa-user-circle',//icon类名
text: '我的',
path: ''//路由路径
}
])
function renderItem (){
return tanBars.map( item =>(
<li key = { item.id }>
<a>
<i className = {'fa ' + item.iconName}></i>
<span> { item.text } </span>
</a>
</li>
) )
}
return(
<footer>
<ul>
{renderItem()}
</ul>
</footer>
)
}
export default TabBar
index.scss
@import '~../../../../assets/global_style/theme.scss';
footer{
width: 100%;
height: .48rem;
ul{
width: 100%;
height: 100%;
display: flex;
justify-content: space-around;
li{
a{
height: 100%;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
i{
font-size: 0.14rem;
margin-bottom: 0.04rem;
}
span{
font-size: 0.14rem
}
&.active{
i{
color: $themeColor;
}
span{
color: $themeColor;
}
}
}
}
}
}
9.图标
-
svg–iconfont中
- 优点:减少ajax请求
- 缺点:增大代码的体积
-
Font Awesome
- 引入cdn
10.移动端1px是有兼容的
1px rem是无法转换的
解决方案:https://www.cnblogs.com/katydids/p/9948546.html -
sass解决方案
@mixin border_bottom($color) { & { position: relative; &:before { content: ""; position: absolute; top: 0; left: 0; border-top: 1px solid $color!important; transform-origin: 0 0; // padding: 1px; box-sizing: border-box; pointer-events: none; } &:last-child:before { border-top:none; } } @media (-webkit-min-device-pixel-ratio:1),(min-device-pixel-ratio:1) { &:before { width: 100%; height: 100%; transform: scale(1); } } @media (-webkit-min-device-pixel-ratio:2),(min-device-pixel-ratio:2) { &:before { width: 200%; height: 200%; transform: scale(0.5); } } @media (-webkit-min-device-pixel-ratio:3),(min-device-pixel-ratio:3) { &:before { width: 300%; height: 300%; transform: scale(0.333); } } }
-
引入方式:
`$
头部引入:
@import ‘~…/…/…/…/assets/global_style/border.scss’;
代码中:
@include border_bottom(#ccc);
$`
11.头部(局部)
-
布局+样式
-
返回的箭头在首页时为无
-
做法:加开关,记住在Layout中定义状态来控制
constructor(props){ super(props) this.state = { goback_flag : false, tab_flag: true } } render() { const { tab_flag, goback_flag } = this.state return ( <div className = "layout"> { tab_flag && <Tab goback_flag = { goback_flag }/>} <Home/> <TabBar/> </div> ) }
-
控制返回箭头(编程式导航)
-
编程式导航
使用js来跳转页面
-
push
-
replace
-
问题: 当我们在tab组件中想要使用路由属性push和replace时,我们发现Tab组件没有,因为它没有被Route使用,所以它不是路由组件
-
解决: 使用高阶组件withRouter,它可以将一个非路由组件转成伪路由组件,让它具备路由属性
做法:
import { withRouter} from 'react-router-dom' function App(props) { return ( <div className="App"> <LayOut {...props}/> </div> ); } export default withRouter(App);
layout组件接收后,tab组件就有了replace和push属性,然后写一个点击事件的函数来控制即可
-
-
-
// 头部 import React from 'react' import './index.scss' const Tab = props => { function goBack(){ //编程式导航 //prop.history.replace("/home") // props.history.push("/home") props.history.go(-1) } return( <header> { props.goback_flag && <i className = "fa fa-chevron-left" onClick = { goBack }></i>} <h3> 首页 </h3> </header> ) } export default Tab
-
12.中间(局部)
具有路由效果
路由:(文档在react-路由中)
-
安装:
$ yarn add react-router-dom
-
可以在LayOut.js中引入路由组件:`$ import { Route} from ‘react-router-dom $’
-
也可以在src下新建react.config.js,写路由(一个组件一个路由,渲染),并在全局index.js下路由模式,并在layout.js中引入
-
加跳转链接,实现路由跳转:引入NavLink
router.config.js import React,{useState}from 'react' import { Route,Switch,Redirect } from 'react-router-dom' import Home from './pages/home' import Category from './pages/category/index' import Recommend from './pages/recommend/index' import ShopCar from './pages/shopcar/index' import Mine from './pages/mine/index' import List from './pages/list/index'; import Detail from './pages/detail/index' import Error from './pages/error/index' //懒加载 // const Home = () =>{ import './pages/home'} const RouterComp = props => { const [routes] = useState([ { id:1, path:'/home', component:Home }, { id:2, path:'/category', component:Category }, { id:3, path:'/recommend', component:Recommend }, { id:4, path:'/shopcar', component:ShopCar }, { id:5, path:'/mine', component:Mine }, { id:5, path:'/mine', component:Mine }, { id:6, path:'/list', component:List }, { id:7, path:'/detail', component:Detail }, { id:8, path:'', component:Error }, ]) function renderRoutes () { return routes.map( item => <Route key = {item.id} path = {item.path} component = {item.component} exact = {item.exact}></Route>) } return ( /* Switch组件一次只渲染一个Route */ /* Route是一个路由展示组件,通过component属性来确定渲染哪一个组件 */ /* Redirect 是重定向组件 from 来源 to 目标 / /home */ /* exact 完全匹配 /home /home/title */ <Switch> <Redirect from = "/" to = "/home" exact/> { renderRoutes() } </Switch> ) } export default RouterComp
layout.js import React, { Component } from 'react' import Tab from '../components/tab'; import TabBar from '../components/tabbar' // import Home from '../pages/home' import './index.scss' import RouterComp from '../router.config'*** export default class LayOut extends Component { constructor(props){ super(props) this.state = { goback_flag : false, tab_flag: true } } render() { const { tab_flag, goback_flag } = this.state return ( <div className = "layout"> { tab_flag && <Tab goback_flag = { goback_flag }/>} <RouterComp/>*** <TabBar/> </div> ) } }
index.js /* react-route 5 提供了两种路由模式 HashRouter BrowserRouter ! HashRouter #/home hashchange事件 兼容高 ! BrowserRouter /home h5 popchange事件 */ import {BrowserRouter as Router} from 'react-router-dom' ReactDOM.render( <Router> <App /> </Router> , document.getElementById('root'));
二级路由
-
同一级路由相似
list页面二级路由示例 import React,{useState} from 'react' import './index.scss' import {NavLink,Route} from 'react-router-dom' import Picture from './Picture'; import Head_phone from './Head_phone'; import Text from './Text'; const List = props => { const [navs] = useState([ { id: 1, text: '壁纸', path: '/list/picture' }, { id: 2, text: '头像', path: '/list/head_phone' }, { id: 3, text: '文字', path: '/list/text' } ]) function renderNavs (){ return navs.map( item => <NavLink key = {item.id} to = {item.path}>{item.text} </NavLink>) } return( <div className = "list-box"> {/* 导航 */} <div className = "list-nav"> {renderNavs()} </div> {/* 长列表 */} <Route path = "/list/picture" component = { Picture }/> <Route path = "/list/head_phone" component = { Head_phone }/> <Route path = "/list/text" component = { Text }/> </div> ) } export default List
13.组件库(复习)
-
引入组件库:yarn add antd-mobile -D
-
按需引入:yarn add react-app-rewired customize-cra -D
-
按照官网步骤进行:yarn babel-plugin-import --save-dev
-
重启项目
-
一轮播为例:可以使用swiper也可也使用组件库Ant Design
- 此为组件库示例
- 选择需要的效果引入
-
解决滑动错误
-
在index.css文件中加
*{ touch-action:none; }
-
14.home页面的列表滑动(局部)
-
头部
- 布局+样式
- 配置路由 -
-
中间盒子(此结构必须为中间盒子加长列表)
-
长列表
-
样式:必须将高度撑开,height:100%
-
配置反向代理
-
新建src/setupProxy.js
-
安装:yarn add http-proxy-middleware —网址:npm.js(根据文档配置)
-
示例
const proxy = require( 'http-proxy-middleware' ) module.exports = function ( app ) { // app.use( proxy(标识符,options) ) // http://m.maoyan.com/ajax/movieOnInfoList?token= app.use( proxy('/ajax',{ target: 'http://m.maoyan.com', changeOrigin: true })) // app.use( proxy() ) }
-
-
-
长列表数据请求,并渲染数据
- useEffect中做数据请求 ---阻止一直进行数据请求:if( movies.length != 0 ) return; - 利用axios来做数据请求,axios要进行封装(待复习) - 渲染数据+结构+样式 - 长列表滚动:学习better-scorll网址 [https://ustbhuangyi.github.io/better-scroll/doc/zh-hans/#better-scroll%20%E6%98%AF%E4%BB%80%E4%B9%88](https://ustbhuangyi.github.io/better-scroll/doc/zh-hans/#better-scroll 是什么) - 安装:yarn add better-scroll -S - 引入:import BScroll from 'better-scroll' - ```js //在导航页面应用BScroll import React,{useEffect} from 'react' import MovieList from './MovieList'; import BScroll from 'better-scroll'; const MovieHot = props =>{ useEffect(()=>{ new BScroll(document.querySelector('.movie-hot-box')) }) return( <div className = "movie-hot-box"> <MovieList/> </div> ) } export default MovieHot ``` -
-
movieItem上加跳转事件
-
想要用js来写必须要用编程式导航
function goDetail() { props.history.push('/detail') }
-
长列表上有一层遮罩层,需要去掉,否则点不到
useEffect(()=>{ new BScroll(document.querySelector('.movie-hot-box'),{click: true} ) })
-
-
-
配置路径别名
-
config.overrides.js配置文件【npm.js-- customize-cra --api.doc --addWebpackAlias(alias)】
// webpack配置文件 const {addWebpackAlias } = require('customize-cra'); const path = require ('path'); function pathResolve( alias ){ return path.join(__dirname,alias) } //路径别名配置 addWebpackAlias({ //! 路径别名:对应的绝对路径 '@':pathResolve('./src'), 'gstyle':pathResolve('./src/assets/global_style'), 'comps':pathResolve('./src/components'), 'layout':pathResolve('./src/layout'), 'pages':pathResolve('./src/pages'), 'utils':pathResolve('./src/utils'), }) );
-
-
15.分类-列表-详情(仿照亲亲网)
- 分类
-
首先先配置反向代理,获得分类页面的数据
-
axios的封装(request.js)–必会 —npm.js上有文档学习,也可以使用中文文档( https://www.kancloud.cn/yunye/axios/234845 )/( http://www.axios-js.com/zh-cn/docs/ )
//request.js import axios from 'axios' // axios默认配置 axios.defaults.baseURL = 'https://api.example.com';//后端接口地址和域名还有端口 // axios.defaults.headers.common['Authorization'] = AUTH_TOKEN; axios.defaults.headers.post['Content-Type'] = 'application/x-www-form-urlencoded'; axios.create({ timeout:1000 }) const request = ({ //axios配置 url, method, header, params,//get参数携带 data,//post参数携带 withCredentials = false, }) => { return new Promise( (resolve,reject) => { // 数据请求 switch ( method ) { case 'POST': const p = new URLSearchParams() for( var key in data){ p.append( key,data[key] ) } axios({ url, method, header, data: p }).then( res => resolve( res )) .catch( err => reject( err )) break; default: axios({ url, method, heaader, params }).then( res => resolve( res )) .catch( err => reject( err )) break; } // axios拦截 // 添加请求拦截器 axios.interceptors.request.use(function (config) { // 在发送请求之前做些什么 return config; }, function (error) { // 对请求错误做些什么 return Promise.reject(error); }); // 添加响应拦截器 axios.interceptors.response.use(function (response) { // 对响应数据做点什么 return response; }, function (error) { // 对响应错误做点什么 return Promise.reject(error); }); }) } export default request
- 拦截
-
作用:拦截用户无登录操作
常用效果:loading
// 添加请求拦截器
axios.interceptors.request.use(function (config) {
// 在发送请求之前做些什么
return config;
}, function (error) {
// 对请求错误做些什么
return Promise.reject(error);
});// 添加响应拦截器
axios.interceptors.response.use(function (response) {
// 对响应数据做点什么
return response;
}, function (error) {
// 对响应错误做点什么
return Promise.reject(error);
});3. 数据请求 ```js import request from 'utils/request' export default class Category extends Component { constructor(props) { super(props) this.state = { categorys:[] } }
-
async componentDidMount(){
// 先发送数据请求,赋值给组件的状态,在修改数据
const result = await request({
url:’/index.php’,
params:{
r:‘class/category’,
type:1
}
})
// console.log(‘result’,result)
this.setState({
categorys:result.data.data.data
})
}
render() {
return (
<div className = "category-box">
Category
</div>
)
}
}
```
-
对分类页面布局,使用组件库的Tabs
-
渲染数据
-
列表
- 将列表页面的数据请求封装成高阶组件来使用,是多个公用,优化代码:高阶组件
- 页面的loading要写在拦截器中
-
详情
-
加入购物车
import React,{useState} from 'react' import './index.scss' import qs from 'querystring' import {Button,Stepper} from 'antd-mobile' const Detail = props => { const {id,img,shop_name,price,name,juan_price} = qs.parse( props.location.search.slice(1)) const [val,setVal] = useState(1) //修改val的方法 function changeVal(val){ setVal(val) } /* 加入购物车逻辑 ! 真实开发 点击按钮,将这个商品的信息以参数的形式发送给后端,然后后端存数据库 ! 模拟整个过程: 本地存储/node 后端 */ function getShopCar(){ //获取数据 return localStorage.getItem('shopcar') && JSON.parse(localStorage.getItem('shopcar'))|| [] } function saveShopCar(data){ return localStorage.setItem('shopcar',JSON.stringify(data)) } function checkShopCar(){ const data = getShopCar() if( data ){//data存在 return true }else{//data不存在 return false } } function addShopCar(){ /* 1. 先判断是不是第一次添加 */ const t = checkShopCar() /* 2. 选择添加 */ if( t ){ //true 说明data存在,判断是不是同一个商品 const data = getShopCar() /* 判断这个商品在不在这个数组中 */ const flag = data.some(item => item.id == id) if(flag){ // true 说明,这个商品已经在本地存储中了 // 只要给当前这一条加数量即可 data.map( item => { if ( item.id == id ) { item.num += 1 return } }) saveShopCar(data) }else{ // false 说明这个商品不再本地存储中,直接添加 data.push({ id, name, price, img, num:val, juan_price }) saveShopCar(data) } }else{ // false 说明这个商品不再本地存储中,直接添加 const arr = [] arr.push({ id, name, price, img, num:val, juan_price }) saveShopCar(arr) } } return( <div className = "detail-box"> <img src= {img} alt=""/> <h3> {name} </h3> <p> 天猫价:{price}</p> <p> 劵后价:{juan_price}</p> <Stepper style={{ width: '40%', minWidth: '100px' }} showNumber max={10} min={1} value={ val } onChange={ changeVal } /> <Button type = 'primary' onClick = {addShopCar}>加入购物车</Button> </div> ) } export default Detail
-
渲染购物车
- 购物车为空
- 购物车有数据
- 获得token值
-
16.路由传参
路由说明文档 https://reacttraining.com/react-router/web/api/Route
将分类页面的cid传递给列表页面,点击时传递用link 加to 属性
<Link to = {{
pathname: "/list",
search: `?cid = ${item.api_cid} `,
}}
-
两种写法:
router.config.js { id:6, // path:'/list:id',//list/001?a=1&&b=2 path:'/list',//list/?a=1&&b=2 component:List },
17.路由接参:利用location接参,search的处理
list页面做数据请求,路由接参,渲染数据
用node中的qs模块将请求数据中的cid变成动态的
引入:import qs from 'querystring' let [resource,setResource] = useState([]) const getData = async() =>{ if(resource.length != 0) return false; const search = qs.parse(props.location.search.slice(1)) const result = await request({ url: '/index.php', params:{ r: 'class/cyajaxsub', page: 1, cid:search.cid, px: 't', cac_id: '' } }) console.log('result',result) setResource(resource = result.data.data.content) } useEffect(() =>{ getData() })
18.动态路由
19.路由监听
实现头部底部控制
-
layout
import React, { Component } from 'react' import Tab from '../components/tab'; import TabBar from '../components/tabbar' // import Home from '../pages/home' import './index.scss' import RouterComp from '../router.config' export default class LayOut extends Component { constructor(props){ super(props) this.state = { goback_flag : false, tab_flag: true, needGoBack: ['/category','/recommend','/shopcar','/mine'], needTabBar: ['/home','/category','/recommend','/shopcar','/home/movie_hot'] } } componentDidMount () { this.checkGoBack() this.checkTabBar() } componentWillReceiveProps ( nextProps ) { /* this.props.location.pathname 是上一个的路由路径 */ this.checkGoBack(nextProps) this.checkTabBar(nextProps) } checkGoBack = (nextProps) => { const { pathname } =nextProps && nextProps.location || this.props.location const f = this.state.needGoBack.some( item => item ==pathname ) if ( f ) { //true 要加 this.setState({ goback_flag: true }) } else { // false 不加 this.setState({ goback_flag: false }) } } checkTabBar = (nextProps) => { const { pathname } =nextProps && nextProps.location || this.props.location const f = this.state.needTabBar.some( item => item == pathname ) if ( f ) { //true 要加 this.setState({ tab_flag: true }) } else { // false 不加 this.setState({ tab_flag: false }) } } render() { const { tab_flag, goback_flag } = this.state return ( <div className = "layout"> { <Tab {...this.props} goback_flag = { goback_flag }/>} <RouterComp/> {tab_flag && <TabBar/>} </div> ) } }
-
tab
// 头部 import React from 'react' import './index.scss' const Tab = props => { function goBack(){ //prop.history.replace("/home") // props.history.push("/home") props.history.go(-1) } var obj = { '/home/movie_hot': '首页', '/category': '分类', '/recommend': '9.9包邮', '/shopcar': '购物车', '/mine': '我的' } const {pathname} = props.location return( <header> { props.goback_flag && <i className = "fa fa-chevron-left" onClick = { goBack }></i>} <h3> {obj[ pathname ]} </h3> </header> ) } export default Tab