React 简介
React是一个用于构建用户界面的JavaScript库,主要用于构建UI,因此一般被用来MVC中V层。React 可使用JavaScript 也可使用JSX,JSX会被babel编译为React.createElement(),React.createElement()将返回一个“ReactElement”的JS对象,且使用JSX语法糖降低学习成本和开发效率。
React 优势
1.声明式设计 −React采用声明范式,可以轻松描述应用。
2.高效 −React通过对DOM的模拟,最大限度地减少与DOM的交互。
3.灵活 −React可以与已知的库或框架很好地配合。
4.JSX − JSX 是 JavaScript 语法的扩展。React 开发不一定使用 JSX ,但我们建议使用它。
5.组件 − 通过 React 构建组件,使得代码更加容易得到复用,能够很好的应用在大项目的开发中。
6.单向响应的数据流 − React 实现了单向响应的数据流,从而减少了重复代码,这也是它为什么比传统数据绑定更简单。
React 函数调用
函数调用方法:
1.react 事件触发切记别带(),直接this.函数名
2.利用箭头函数:οnclick={()=>this.函数名(参数)},该方法比较便于传参 ;
3.在Dom节点加上‘{}’,并在其中直接写函数方法。
数据绑定(单向绑定)
1.绑定数据在input,首先通过定义变量const 自定义名 = React.createRef();其次在<input ref={this,自定义名} 实现绑定;
2.若有绑定事件,DOM节点触发事件最好用箭头函数,不然得用bind指向this,避免this指向undefined。例如 οnclick={this.函数名.bind(this)}
响应式数据
react 数据非Object.defineProperty 机制,非通过setter/getter劫持数据,它是通过diff进行VDOM重新渲染。在rend函数外,用内置state:{要定义的数据名:数据},然后在方法钟通过this.setState({定义的数据名:修改后的数据})。也可通过构造函数去注册函数,constructor(){ super();//必须写继承方法 this.state{自定义名:数据}},然后在方法钟通过this.setState({自定义名:修改后的数据})
状态管理小Tip
通过拓展运算符进行数据拷贝,将拷贝数据setState改变旧数组,其原理使用深拷贝避免直接操作原数据。
组件通信
父传子
子组件封装
export default class NavBar extends Component {
//状态管理
state={
show:0
}
render() {
//对象转数组
const List =Object.values(this.props);
const Tem = List.map((item,index)=>(<li onClick={()=>{this.selected(index)} } className= { this.state.show ===index ? 'active NavItem':'NavItem'} key={index}>{item}</li>));
return (
<div>
<ul className='NavBox'>
{Tem}
</ul>
</div>
)
}
selected(index){
this.setState({
show:index
})
}
}
// 默认传参
NavBar.defaultProps={
List:['首页']
}
父组件传参
import NavBar from '../comment/NavBar/NavBar';
import React from 'react';
export default function index() {
const List =['首页','服饰','电器','食品','宠物','其他'];
return (
<>
//通过拓展运算符传参
<NavBar {...List}></NavBar>
</>
)
}
子传父
//父组件引用NavBar子组件,定义在组件中定义函数接收子组件传来参数
<NavBar op={(参数)=>{代码块}}></NavBar>
//NavBar子组件传参
<button onClick=>{this.props.op(参数)}>子按钮</button>
组件化开发小Tip
组件尽量不设状态,直接使用父组件进行属性传参,避免循环渲染导致状态冲突。例如子组件有自我状态,如果父组件要求改变子组件状态,会导致两种状态并存。
非父子组件传参
1、通过父组件作为媒介,将主请求交给父组件,请求数据交予子组件,子组件间的交互交予父组件去传输。
2、订阅发布模式;
3、context,
插槽
//子组件 Header.js
render(
const {children} = this.props;
<div>
//预留插槽
<span>{children[0]}</span>
<span>{children[1]}</span>
<span>{children[2]}</span>
</div>
)
//父组件引用
//引入Header.js
retrun(
<>
<Header>
<button>返回</button>
<span>标题</span>
<span>设置</span>
</Header>
</>
)
生命周期
旧版生命周期
初始化阶段
componentWillMount(已舍弃):render之前最后一次修改状态的机会;
render:只能访问this.props和this.state,不允许修改状态和DOM输出;
componentDidMount:成功渲染之后触发,可修改DOM。
运行阶段
componentWillReceiveProps (已舍弃):子组件可监听父组件属性修改;
shouldComponentUpdate:返回true/false,阻止render调用;
componentWillUpdate:在接收新的属性和状态的时候,可对该周期进行DOM操作,可做动画,但是无法修改属性和状态,因为会导致死循环。
render:只能访问this.props和this.state,不允许修改状态和DOM输出;
componentDidUpdate:可修改DOM。
销毁阶段
componentWillUnmount:删除组件前的所有操作的周期。
此方法用于组件卸载前,清理一些注册监听事件,或者取消订阅的网络请求等
一旦一个组件实例被卸载,其不会被再次挂载,而只可能是被重新创建
新增生命周期
getDerivedStateFromProps(props,state)
该方法会在调用 render 方法之前调用,当组件props变化时state更新。该方法是回调函数,若为空,不做任何修改;若需要DOM渲染前对状态做调整,可通过返回state进行修改(注:该函数无需this指向state)
小Tip
通过getDerivedStateFromProps(props,state),提前备份存储这一次渲染属性,然后在componentDidUpdate(props,state)里比较上次的渲染属性,若相等直接return,若不相等执行网络请求,避免死循环。例如:
state={
type:'start';
}
static getDerivedStateFromProps(props,state){
return type=props.父传属性值
}
componentDidUpdate(props,state){
if(this.state.type==='start'){
//请求模块
}
else if(this.state.type === props.type){
return
}else{
//请求模块
}
}
getSnapshotBeforeUpdate
该生命周期取代componentWillUpdate,触发时间是render之后DOM渲染之前,并返回一个值作为componentDidUpdate的第三个参数(属性props,状态state,componentWillUpdate返回的值)
小Tip
结合以上两个生命周期,获取DOM渲染前的数据和渲染后的数据,进行对比数据变化。例如实现位置定位:
//获取渲染前滚轮高度
getSnapshotBeforeUpdate(){
return this.refs.ww.scrollTop;
}
//实现定位当前高度
componentDidUpdate(props,state,preHeight){
this.refs.ww.scrollTop += this.refs.ww.scrollHeight-preHeight
}
<div style={height:'300px',overfloat:'auto' ref='ww'}>
<ul><li></li>...</ul>
</div>
常用:
render
类组件必须实现的方法,用于渲染DOM结构,可以访问组件state与prop属性
注意:不要在 render 里面 setState, 否则会触发死循环导致内存崩溃
componentDidMount
组件挂载到真实DOM节点后执行,其在render方法之后执行此方法多用于执行一些数据获取,事件监听等操作
shouldComponentUpdate
控制组件更新,通过return true/false来控制生命周期是否进行更新。它自带两个参数(上一个props,上一个state),可通过该生命周期来控制更新频率,优化性能,例如:
shouldComponentUpdate(nextProps,nextState){
//通过新旧状态,进行对生命周期的调用控制
if(JSON.stringify(this.state) != JSON.stringify(nextState)){
return true
}else{
return false
}
}
避免发送请求死循环
1.避免重复加载的方法除了以上演示,还能通过引入React 内置PureComponent进行新旧props和state进行比较,避免重复刷新,由此优化性能。但是对于必然会更新的组件不建议使用该方法,避免强制校验导致性能损耗。代码如下:
import {PureComponent} from 'react'
export default class NavBar extends PureComponent{
//代码块
}
2.通过useEffect(),实现一次仅此一次render调用该hook钟的网络请求
import {useEffect} from 'react'
useEffect(()=>{
//axios网络请求
},[])
//空数组,代表无需依赖,若引用函数内的状态值或者传参,则在数组中传参
//当不传递第二个参数时,每次render都会执行一遍callback函数,相当于包含第一遍render的componentDidUpdate
//当传递第二个参数且是空数组时,只有第一次render才会执行callback,类似于componentDidMount
//不管是否传递第二个参数,只要在callback中return 一个函数,就相当于告诉react此组件挂掉之前执行什么操作,类似于componentWillUnMount
useEffect和useLayoutEffect如何抉择?
useEffect发生在视图更新后,而layoutEffect发生在编译成dom元素之后视图更新之前。
两者的执行顺序不同,定位也不同,由于操作DOM需要一定时间,我们最好优先使用useEffect,以保证用户页面第一时间渲染出来。
如果业务需求是先要进行DOM操作或者跟页面布局相关的,那么就可以使用useLayoutEffect
componentWillReceiveProps
该生命周期可以让子组件在不影响父组件传参的条件下,加工或者存储父组件参数到子组件。例如:
componentWillReceiveProps(nextProps){
//给传属性后缀加工111
this.setState({
content:nextProps.传的参数名 + '111'
})
}
componentDidUpdate
执行时机:组件更新结束后触发
在该方法中,可以根据前后的props和state的变化做相应的操作,如获取数据,修改DOM样式等(可通过该生命周期追溯变更后的上一次的props和state)
优化
useCallback(优化函数方法)
使用该hook函数,可避免函数不断被setter重定义,实现缓存的作用。只有第二参数(该hook数组绑定参数)发生变化,useCallback函数才重新定义一次。
import [useCallback] from 'react;
const 函数名=useCallback(()=>{
//逻辑代码
},[绑定数据数组]) //数组为空情况下,响应式数据落绑,导致空值
useMemo(优化计算属性)
类似vue计算属性,缓存计算结果,当计算结果发生变化,重新执行/定义内部函数。
Route 路由
引入路由
npm i react-router-dom@5
Switch判断切换多个路由,必备;
Route 路由注册,必备;
Redirect 路由重定向.
import { Switch, Route, Redirect } from 'react-router-dom'
import {HashRouter,Route,Switch,Redirect } from 'react-router-dom'
import Login from '../pages/Login.js'
import Resiger from '../pages/resiger.js'
import NoFind from '../pages/NoFind.js'
import More from '../pages/More.js'
export default class IndexRouter extends Component {
render() {
return (
<div>
<HashRouter>
{/* Switch+Redirect 路由重定向 */}
<Switch>
<Route path='/Login' component={Login}/>
<Route path='/Resiger' component={Resiger}/>
<Route path='/More' component={More}/>
{/* Redirect 必须先注册路径才能正常使用 */}
<Redirect from='/' to='/Login' exact/>
{/* 一般重定向是模糊的,加上‘exact’变为精确,避免任何正规与非正规路径定向到默认路径 */}
{/* <Redirect from='/' to='/Login' exact/> */}
{/* 匹配不到以上条件路径,直接跳转指定404页面 */}
<Route component={NoFind}/>
</Switch>
</HashRouter>
</div>
)
}
}
子路由
如果直接在父级路由那编写子路径,子路径跟父级路径同级导致父级被顶替无法正常显示.
<Route path='/More' component={More}/>
{/* 不可取,会顶替父级路由 */}
<Route path='/More/First' component={Child} />
正确嵌套是通过父级页面引入子路由
import { Switch, Route, Redirect } from 'react-router-dom'
import Child from './Childrens/Child1'
import Child2 from './Childrens/Child2'
export default class More extends Component {
render() {
return (
<div>
<h2>
更多
</h2>
<Switch>
<Route path='/More/First' component={Child} />
<Route path='/More/Second' component={Child2} />
<Redirect from='/More' to='/More/First' />
</Switch>
</div>
)
}
}
路由跳转
声明式导航
{/* NavLink和activeClassName都是内置,点击高亮自动匹配 */}
<NavLink to='/路径' activeClassName='自定义高亮类名'></NavLink>
编程式导航
第一种
{/* 函数页面跳转 */}
const handleGo=(id)=>{
props.history.push(`/More/${id}/`)
}
{/* 类页面跳转(this指向) */}
const handleGo=(id)=>{
this.props.history.push(`/More/${id}/`)
}
第二种 引入内置方法
{/* 引入 */}
import {useHistory} from 'react-router-dom'
{/* 存储*/}
const history = useHistory();
const handleGo=(id)=>{
history.push(`/More/${id}/`)
}
路由传参
query传参
history.push({pathname:‘/路径’,query:{传参}})
** 动态路由传参 **
history.push(/路径、${参数}
)
** state传参**
history.push({pathname:‘/路径’,state:{传参}})
路由拦截
<Route path=‘/权限路径’ render={()=>{return isAuth()?<页面/>:}}/>
给跳转页面传参
第一种
<Route path='/权限路径' render={(props)=>{return isAuth()?<跳转页面名 传参名=‘’ {...props}/>:<Redirect to='重定向页面'>}}/>
{/* 跳转页面函数获取参数*/}
function 跳转的页面名(props){
console.log(props)
}
第二种 引入withRouter()
{/* 跳转页面函数获取前一页面参数*/}
import {withRouter} from 'react-router-dom'
function 跳转的页面名(props){
console.log(props)
}
{/* withRouter()将跳转页面作为前一页面的子页面*/}
export default withRouter(要跳转的页面名)
-----------------------------------------
{/*前一级路由---跳转前 props因为跳转页面withRouter(),导致跳转后有参数传出*/}
<Route path='/权限路径' render={(props)=>{return isAuth()?<跳转页面名 传参名=‘’ {...props}/>:<Redirect to='重定向页面'>}}/>