在 src 目录下新建一个名为
setupProxy.js
的文件(必须是这个文件名),先要安装cnpm i http-proxy-middleware -D
,再const proxy = require('http-proxy-middleware')
引入。
const proxy = require('http-proxy-middleware')
module.exports = function(app){
// app.use(proxy('标识符','配置项')),这个可以多个,配置多个可反向代理网站的反向代理跨域
app.use(proxy('/ajax',{
target:'http://m.maoyan.com',
changeOrigin:true,
}))
app.use(proxy('/index.php',{
target:'http://www.qinqin.net',
changeOrigin:true,
}))
}
import React from 'react';
要在js文件中写 jsx 语句就要引入这句
import ReactDOM from 'react-dom';
要在web上运行就要引入这句(一般在入口文件中写上这句就可以了,因为其它的js文件都是要往这里引入的)
'react'和'react-dom'
在用脚手架创建项目时就会安装
要使用路由,得先安装
'cnpm i react-router-dom -S'
//as的作用就是给BrowserRouter起一个别名,用Router来包裹App,就可以使用路由了
import {BrowserRouter as Router} from 'react-router-dom'
//引入antd的css样式文件
import 'antd-mobile/dist/antd-mobile.css'
ReactDOM.render(
, document.getElementById('root'));
1、下面所要用到的都有可以在
'react-router-dom'
里面解构出来2、
import {Route,Redirect,Switch} from 'react-router-dom'
3、Redirect表示路由重定向,一般只有这个才需要添加exact 路径完全匹配
4、Switch代表一次只会渲染一个路由配置
5、
代表错误路由配置,这里不需要写路径path
6、这里的
代表路由展示区,path为所要到的路径,component代表所要用的路由组件。
路由导航的标签
或者
都是从
'react-router-dom'
解构出来的
import {NavLink,Link,Route} from 'react-router-dom'
,和
的区别在于是否需要点击时的状态激活,前者可以,后者不行。
还有二级路由的写法,不能在有二级路由的
里面写exact完全匹配。否则会直接变错误路由展示。
{/* login和register的路由导航 */}
登录
注册
{/* login和register的路由展示 */}
1、1px兼容:一般为列表页面每条数据的下面或者页面footer部分的上边的一条1px宽的细线。
2、这里用的是border这个函数
//做1px兼容
import styled from 'styled-components'
const border = ({
component = null,
width = '1px',
style = 'solid',
color = '#ccc',
radius = 0,
}) => {
return styled(component) `
position: relative;
border-width: ${ width};
border-radius: ${ radius + 'px'};
&::after {
pointer-events: none;
position: absolute;
z-index: 999;
top: 0;
left: 0;
content: "";
border-color: ${ color};
border-style: ${ style};
border-width: ${ width};
@media
(max--moz-device-pixel-ratio: 1.49),
(-webkit-max-device-pixel-ratio: 1.49),
(max-device-pixel-ratio: 1.49),
(max-resolution: 143dpi),
(max-resolution: 1.49dppx) {
width: 100%;
height: 100%;
border-radius: ${ radius + 'px'};
};
@media
(min--moz-device-pixel-ratio: 1.5) and (max--moz-device-pixel-ratio: 2.49),
(-webkit-min-device-pixel-ratio: 1.5) and (-webkit-max-device-pixel-ratio: 2.49),
(min-device-pixel-ratio: 1.5) and (max-device-pixel-ratio: 2.49),
(min-resolution: 144dpi) and (max-resolution: 239dpi),
(min-resolution: 1.5dppx) and (max-resolution: 2.49dppx) {
width: 200%;
height: 200%;
transform: scale(.5);
border-radius: ${ radius * 2 + 'px'};
};
@media
(min--moz-device-pixel-ratio: 2.5),
(-webkit-min-device-pixel-ratio: 2.5),
(min-device-pixel-ratio: 2.5),
(min-resolution: 240dpi),
(min-resolution: 2.5dppx) {
width: 300%;
height: 300%;
transform: scale(.33333);
border-radius: ${ radius * 3 + 'px'}
};
transform-origin: 0 0;
};
`
}
export default border
我们利用的就是这个border函数,配合样式组件使用,这里就是要给li标签的下方添加1px细线,最后用样式组件Li替换掉原来的li标签就可以了。
import border from '../../components/border.js'
import styled from 'styled-components'
const Li = border({
component:styled.li`
padding:.1rem 0;
`,
width : '0 0 1px 0',//这里可以控制线的位置
style : 'solid',//细线类型
color : '#ccc',//细线颜色
radius : 0,//细线圆角
})
1、只有使用了Route的组件身上才有路由属性:history、location、match
2、具有以上三个属性的组件,称为路由组件
引入withRouter:
import { withRouter } from 'react-router-dom'
可以使用高阶组件withRouter,高阶组件是一个函数,接收一个组件作为参数,
比如:
export default withRouter(LayOut)
可以将非路由组件LayOut转化为伪路由组件,其上有history、location、match
React和Vue不太相同,前者感觉比较宽松些,Vue的话,一般是在
'/category/:id'
这样才能成为动态路由,而虽然React的写法有两种,一种是和Vue一样,另外一种是直接写成'/category'
后面不加'/:id'
。第一种,写了id,在路径上就必须有id值,也就是路径全要跟上,否则会报错。第二种,写id也可,不写也行,相对比较宽松。
路由传参,一般是在路由导航
NavLink
或者Link
上写to={{}}
这个配置项,不传参时可以直接写to='/home'
,要传参时,可以to={{path:'/home',search:{键:值,}}}
这里是两个大括号。
路由接参,一般在另外一个页面,在
this.props
上的location的search上可以找到传过来的id、查找字符串等信息,然后querystring操作,将问号截去且将字符串转成对象,就可以进行axios数据请求了。
这里采用的是第三方插件better-scroll,虽然
overflow:auto
也可以解决,但是性能没有前者好。better-scroll官网
1、安装
cnpm i better-scroll -S
2、引入
import BScroll from 'better-scroll'
3、实例化:在componentDidMount(){ }这个钩子里面进行
render() {
return (
// 通过ref绑定,获取当前组件
<ContentBox ref = {el=>this.ContentBox=el}>
<ul>
{ this.renderItem() }
</ul>
</ContentBox>
);
}
componentDidMount () {
setTimeout(()=>{
// 实例化当前组件
this.bscroll=new BScroll(this.ContentBox,{
click:true,//添加这个better-scroll相当于在最上层加了一层遮罩,添加这个配置项,使能click
pullUpLoad:{//上拉加载
threshold:50,//上拉50px开始加载
}
})
this.bscroll.on('pullingUp',()=>{//这是上拉加载事件
axios({//这里一般进行上拉加载的数据请求
url:'/index.php',
params:{
r:str.r,
page:count++,
cid:str.cid,
px:'t',
cac_id:cacid,
},
}).then(res=>{//这里可以将上拉加载数据请求得到的数据添加到第一次数据请求得到的数据的后边,这样就可以将这些数据连续的渲染出来
this.state.food.push(...res.data.data.content)
this.setState({//更新视图
food:this.state.food
})
})
.catch(error=>console.log(error))
this.bscroll.finishPullUp()//松开代表一次上拉结束,可以继续上拉请求新数据,如果还有数据的话。
})
},0)
}
//要实例化这个要滚动的部分,即这个最大的组件ContentBoX,注意这里是能鼠标或手滚动,不是鼠标滑动。
/**
* axios拦截器
*
* 1、数据请求前进行拦截,完成任务
*
* 2、数据请求后进行拦截,完成任务
* */
import axios from 'axios'
import {Toast} from 'antd-mobile'
// Add a request interceptor
axios.interceptors.request.use(function (config) {
Toast.loading('Loading...', 1, () => {//loading加载文字、延迟时间、回调函数,都可修改
console.log('Load complete !!!');
});
// Do something before request is sent
return config;
}, function (error) {
// Do something with request error
return Promise.reject(error);
});
// Add a response interceptor
axios.interceptors.response.use(function (response) {
Toast.hide()
// Do something with response data
return response;
}, function (error) {
// Do something with response error
return Promise.reject(error);
});
作用:可以使函数组件(无状态组件)能够自己定义状态数据state和使用类似于生命周期钩子的函数来操作Dom和书写一些逻辑。
官网hooks例子:这里有个小问题是,如果将button里面的count+1改为count++就表现为点击两次才加1。
import React, { useState, useEffect } from 'react';
function Example() {//这里是函数组件
const [count, setCount] = useState(0);//count为状态数据、setCount为修改count的回调函数、
//useState为从react中解构出来的API,用来给count设置初始值。
// useEffect的作用与 componentDidMount 和 componentDidUpdate 类似:
useEffect(() => {
// Update the document title using the browser API
document.title = `You clicked ${count} times`;
});
return (
<div>
<p>You clicked {count} times</p>
<button onClick={() => setCount(count + 1)}>
Click me
</button>
</div>
);
}
1、原生实现:递归
2、工具实现:
(1)、lodash (这种拷贝方式有缺点)
1、兼容性:IE11可以,之前的版本有兼容性
2、浅拷贝:只能拷贝一层
3、深拷贝:会把所有节点都复制一遍,有性能损耗
(2)、immutable.js
什么是不可变数据:
不可变数据 (Immutable Data )就是一旦创建,就不能再被更改的数据。对 Immutable 对象的任何修改或添加删除操作都会返回一个新的 Immutable 对象。Immutable 实现的原理是持久化数据结构( Persistent Data Structure),也就是使用旧数据创建新数据时,要保证旧数据同时可用且不变。同时为了避免 deepCopy 把所有节点都复制一遍带来的s性能损耗,Immutable 使用了 结构共享(Structural Sharing),即如果对象树中一个节点发生变化,只修改这个节点和受它影响的父节点,其它节点则进行共享。
优点:
1、降低immutable带来的复杂度
2、节省内存
3、历史追溯性(时间旅行):时间旅行指的是,每时每刻的值都被保留了,想回退到哪一步只要简单的将数据取出就行,想一下如果现在页面有个撤销的操作,撤销前的数据被保留了,只需要取出就行,这个特性在redux或者flux中特别有用
4、拥抱函数式编程:immutable本来就是函数式编程的概念,纯函数式编程的特点就是,只要输入一致,输出必然一致,相比于面向对象,这样开发组件和调试更方便。推荐一本函数式编程的在线免费书《JS 函数式编程指南》, 此书可以推荐给学生做为课外补充阅读。
缺点:
1、需要重新学习api
2、资源包大小增加(源码5000行左右)
3、容易与原生对象混淆:由于api与原生不同,混用的话容易出错。
const { Map , List } = require('immutable')//ES5写法
import { Map , List } from 'immutable'//ES6写法也可
const map = Map({//map这个对象是不会改变的
a: 1,
b: 2,
c: 3
})
const newMap = map.set('b', 20) // immutable数据每次都是生成新的再重新调用set进行修改,所以需要 重新赋值给一个新的变量,这里要修改或者添加newMap里面的值,只能通过map.set(),虽然newMap也是immutable对象,但newMap.set()是无法修改或添加的.
console.log(map, newMap) // immutable.Map不是原生的对象
console.log(map.b, newMap.b) // immutable.Map不是原生的对象,不能直接像对象一样取值,所以是undefined
console.log(map.get('b'), newMap.get('b')) // 要取值,需要调用get(key)方法,可以看到,两个值不一样
const obj = {
a: 1,
b: 2,
c: 3
}
console.log(Map.isMap(map), Map.isMap(obj),Map.isMap(newMap)) // true false true, 使用Map.isMap来判断是否是一个immutable.Map类型
//数组List和Map是相似的,List.isList()判断