一、组件三大属性
每个组件的this上都有三个重要属性:
- state
- props
- refs
1.state
state称为状态,状态驱动着页面的改变
class Person extends Component {
state = {
name="张三",
age:18
}
/*
此处必需写箭头函数
不然按钮处就需要写this.change.bind(this),确保this不丢失
*/
change=()=>{
/*
更新状态是合并,不是覆盖,即传入参数{name:"李四"}
与原装胎对比,不会覆盖原状态中的age
*/
this.state({name:"李四"})
}
render() {
return (
<div>
{<span>{this.state.name}</span>}
<button onClick={this.change}>点我更新状态</button>
</div>
)
}
}
2.props
父组件传递给子组件的参数
class Father extends Component {
state = {
bear:{
x:1,
y:2
}
}
render() {
return (
<div>
<Child x={1} y={2}></Child>
</div>
)
}
}
/*
或者:
将bear对象结构,得到x,y,相当于分别传递
<Child {...this.state.bear}></Child>
*/
子组件中可对接收到的参数进行限制,需引入React库中的PropTypes进行限制
//npm i prop-types
import PropTypes from 'prop-types'
class Child extends Component {
//静态属性
static propTypes={
//支持链式写法
x:PropTypes.string.isrequired
//类型以小写开头,以区别原生,如:string,object,bool
//ps:函数类型:func
}
//默认值
static defaultProps={
x:1
}
render() {
return (
<div>
{this.props.x}
</div>
)
}
}
/*
或者:
Child.propTypes={
x:PropTypes.func
}
*/
3.refs
使用ref可直接拿到组件的原生DOM对象
//有三种写法获得ref
//1.字符串式(不推荐)
class Person extends Component {
render() {
console.log(this.refs)
return (
<div>
<span ref="demo"></span>
</div>
)
}
}
//2.回调函数式
class Person extends Component {
/*
函数式会将ref作为参数传递给回调函数,即c,
可直接写在类实例属性上,即this.test=c,
这样可不用去refs里取
*/
render() {
return (
<div>
<span ref={c=>{this.test=c}}></span>
</div>
)
}
}
二、生命周期
旧生命周期

初始化
- constructor(构造函数)
- componentWillMount(将要挂载)
- render(渲染函数)
- componentDidMount(组件已经挂载)
更新时
- shouldComponentUpdate(控制是否更新)
- componentWillUpdate(组件将要更新)
- render(渲染函数)
- componentDidUpdate(组件已经更新)
卸载时
可由ReactDOM.unmountComponentAtNode()手动触发组件卸载
- componentWillUnmount(组件将要卸载)
注意
- shouldComponentUpdate控制是否允许此次更新,返回true则允许更新,继续后面流程,返回false则中断后面流程,不返回值则报错。
- 组件上的forceUpdate()方法,可强制更新,跳过shouldComponentUpdate控制。
新生命周期

初始化
在constructor和render之间加入getDerivedStateFromProps钩子,用法比较罕见,一般在state全受prop控制时使用,以下摘自官网:

更新时
同初始化时,在shouldComponentUpdate前加入钩子:getDerivedStateFromProps,不过还在componentDidUpdate前加入,getSnapshotBeforeUpdate(更新前获得组件快照),用法也比较罕见,以下摘自官网:

卸载时
同旧的生命周期钩子。
即将废弃的钩子
- componentWillMount
- componentWillReceiveProps
- componentWillUpdate
使用会出现警告,最好不使用,或者前面带上UNSAFE_前缀来使用。
总结
新的生命周期相交于旧的
废弃了三个不重要的钩子:将要挂载,将要收到参数,将要更新
更新了两个罕见的钩子:获得props驱动的state,更新前获得组件快照
总的来说,重要的钩子有三个:
- render,渲染函数
- componentDidMount,组件挂载完成,一般做发起请求,初始化等工作。
- componentWillUnmount,组件即将卸载,一般做收尾工作。
三、React原理简述
React组成
- react.js,React核心库。
- react-dom.js,提供操作DOM的react核心库,依赖于上述库。
- babel.js,用于解析jsx,将其转换为js代码的库。
Vitual Dom和Diff算法
相较于直接操作较大的真实(与虚拟相对)DOM对象,操作JS里的对象可优化浏览器性能,减少重排和重绘的次数。
故将真实的浏览器DOM对象映射成JS里的对象(虚拟DOM),每次数据更新,驱动视图的改变时,查看虚拟DOM有没有受影响,未受影响,则复用,否则重新渲染。
//每个dom都有一个key值,用以区分每个虚拟DOM
class Person extends Component {
state = {
arr=[
{id:1,name:'老张',age:30},
{id:2,name:'老王',age:20}
]
}
render() {
//此处key值不推荐(不是不可以)使用数组下标index
const {arr}=this.state
return (
<ul>
arr.map((item,index)=>{
return <li key={index}>{item.name}-{item.age}</li>
})
</ul>
)
}
/*
render() {
//此处key值不推荐使用数组下标index
若dom中有一些"未改变"(要复用的DOM)(若正好是输入类,则很危险)
则一些破坏原数组,如:逆序插入
的方法可能导致每个li和input错位
const {arr}=this.state
return (
<ul>
arr.map((item,index)=>{
return <li key={index}> {item.name}-{item.age} <input/></li>
})
</ul>
)
}
*/
}
四、Router路由
1.实现原理
SPA(Single Page APP)
- 只有一个完整页面
- 点击页面中的链接,不会刷新页面,只会做局部更新
原生实现
通过监听Window上的Hash或History的变化,来实现页面的更新,如下:
let history = History.createHashHistory()
function push (path) {
history.push(path)
return false
}
function replace (path) {
history.replace(path)
}
function back() {
history.goBack()
}
function forword() {
history.goForward()
}
history.listen((location) => {
console.log('请求路由路径变化了', location)
})
前进和后退对应浏览器URL左边的:
2.使用
react-router-dom提供组件:
- BrowserRouter、HashRouter,一般包在APP组件外边,决定是哪种路由方式。
- Route 注册路由
- Redirect 重定向
- Link、NavLink、Switch 决定路由跳转
例一:Route和Switch的使用
入口文件:
import React from 'react'
import ReactDOM from 'react-dom'
import { BrowserRouter } from 'react-router-dom'
import App from './App'
ReactDOM.render(
<BrowserRouter>
<App />
</BrowserRouter>,
document.getElementById('root')
)
App:
import React, { Component } from 'react'
import {NavLink,Route} from 'react-router-dom'
import Home from './pages/Home' //Home是路由组件
import About from './pages/About' //About是路由组件
import Header from './components/Header' //Header是一般组件
export default class App extends Component {
render() {
return (
<div>
<div>
<div>
<Header/>
</div>
</div>
<div>
<div>
<div>
{/*类似于原生中的a标签 */}
<NavLink to="/about">About</NavLink>
<NavLink to="/home">Home</NavLink>
</div>
</div>
<div>
<div>
<div>
{/* 注册路由 */}
<Route path="/about" component={About}/>
<Route path="/home" component={Home}/>
</div>
</div>
</div>
</div>
</div>
)
}
}
NavLink和Link差别:NavLink可给activeClassName,activeStyle等属性
Switch作用:只渲染一个匹配到的第一个路由,不会渲染多个,如下:
//若无Switch包裹,当路径为/home时以下三个组件皆渲染
<Route path="/home" component={Home}/>
<Route path="/:user" component={User}/>
<Route component={NoMatch}/>
//包裹起来确保只渲染匹配到的第一个
<Switch>
<Route path="/home" component={Home}/>
<Route path="/:user" component={User}/>
<Route component={NoMatch}/>
</Switch>
例二:精准匹配和模糊匹配
//路由导航
<MyNavLink to="/about">About</MyNavLink>
<MyNavLink to="/home/a/b">Home</MyNavLink>
//注册路由
<Switch>
<Route exact path="/" component={Root} />
<Route exact path="/about" component={About}/>
<Route exact path="/home" component={Home}/>
</Switch>
**exact表示开启路由精准匹配,**如:路径/home/a/b必需精准对应某个路由的path,该路由才能进行渲染。
Route默认是模糊匹配,如:路由Root的path是/home/a/b的字串,匹配上了,优先进行渲染;若未使用Switch组件,则Root和Home一并渲染。
工程中一般都是模糊匹配
例三:重定向
<Switch>
<Route path="/home" component={Home}/>
<Route path="/about" component={About}/>
<Redirect to="/about" />
</Switch>
重定向组件一般对路由进行兜底,如上,若路径/home和/about都未匹配上,则使用Redirect组件,发现其路径是/about,故匹配About组件。
例四:嵌套路由
App:
<Switch>
<Route path="/home" component={Home}/>
<Route path="/about" component={About}/>
<Redirect to="/about" />
</Switch>
Home:
<MyNavLink to="/home/news">News</MyNavLink>
<MyNavLink to="/home/message">Message</MyNavLink>
<Switch>
<Route path="/home/news" component={News}/>
<Route path="/home/message" component={Message}/>
<Redirect to="/home/news"/>
</Switch>
3.路由传参
路由组件的props
history:{
action:"PUSH",
length:4
location:{....}
}
location:{
hash: ""
key: "t3t15u"
pathname: "/home/message/detail/"
search: "?id=5&title=消息1"
state: undefined
}
match:{
isExact: true
params: {}
path: "/home/message/detail"
url: "/home/message/detail/"
}
例一:传递params参数
<Link to={`/home/message/detail/5/${title}`}>新闻标题</Link>
//声明接受params参数
<Route path="/home/message/detail/:id/:title" component={Detail}/>
路由组件:
// 接收params参数
const {id,title} = this.props.match.params
例二:传递state参数
实际上,route的to写成字符串是一种简写形式,完整形式应该为一个对象,对象有一个key为state
const routerObj={pathname:'/home/message/detail',state:{id:5,title:'参数'}
<Route to={routerObj}/>
路由组件:
// 接收state参数
const {id,title} = this.props.location.state || {}
例三:传递search参数
//查询字符串
<Link to={`/home/message/detail/?id=5&title=${title}`}>新闻标题</Link>
//正常注册路由
<Route path="/home/message/detail" component={Detail}/>
路由组件:
// 接收search参数
const {search} = this.props.location
const {id,title} = qs.parse(search.slice(1))
路由传参总结
除了params传递参数需要在Route声明接受外,其它的都正常注册,然后组件内部在props上取得参数。
Search查询字符串方法较麻烦,一般不用。
4.高级使用
例一:replace路由
<Link replace to="/home">Home</Link>
replace 表示匹配的路由不是压栈,而是代替栈顶的路由。
例二:编程式路由导航
路由组件:
replaceShow = (id,title)=>{
//replace跳转+携带params参数
//this.props.history.replace(`/home/message/detail/${id}/${title}`)
//replace跳转+携带search参数
// this.props.history.replace(`/home/message/detail?id=${id}&title=${title}`)
//replace跳转+携带state参数
this.props.history.replace(`/home/message/detail`,{id,title})
}
pushShow = (id,title)=>{
//push跳转+携带state参数
this.props.history.push(`/home/message/detail`,{id,title})
}
back = ()=>{
//栈顶指针向下移动1位
this.props.history.goBack()
}
forward = ()=>{
//栈顶指针向上移动1位
this.props.history.goForward()
}
go = ()=>{
this.props.history.go(-2)
}
<button onClick={()=>this.pushShow(5,'test')}>跳转</button>
例三:withRouter的使用
一般组件是没有路由组件上面的hisroty等属性和方法的,所以引入了withRouter。
import { withRouter } from "react-router-dom";
class Header extends Component {
render() {
//可以看到history、location、match属性
console.log(this.props)
return (
<div>
Test
</div>
)
}
}
//包裹一层
export default withRouter(Header)
五、组件通信
1.pubsub-js 消息订阅
pubsub-js是一个消息订阅库,可在任意框架中使用。
//下载
npm install pubsub-js --save
//组件中引入
import PubSub from 'pubsub-js'
//订阅方:
//订阅消息,回调的第一个参数为消息名(delete)(饱受诟病)
this.id= PubSub.subscribe('delete', (title,data)=>{})
//组件销毁前要取消订阅
PubSub.unsubscribe(this.id)
//发布方:
//发布消息
PubSub.publish('delete', '我是参数')
2.redux
redux是一个专门用于做状态管理的JS库(不是react插件库),可在任意框架中使用,但一般只在react中使用。
工作流程:

文件结构:

constant中定义枚举类型
export const INCREMENT = 'increment'
export const DECREMENT = 'decrement'
export const ADD_PERSON = 'add_person'
actions中一般导出一个action的创建函数
import {INCREMENT,DECREMENT} from '../constant'
export const increment = data => ({type:INCREMENT,data})
export const decrement = data => ({type:DECREMENT,data})
export const incrementAsync = (data,time) => {
return (dispatch)=>{
setTimeout(()=>{
dispatch(increment(data))
},time)
}
}
reducers中创建出reducer
import { INCREMENT, DECREMENT } from '../constant'
const initState = 0
export default function countReducer(preState = initState, action) {
const { type, data } = action
switch (type) {
case INCREMENT:
return preState + data
case DECREMENT:
return preState - data
default:
return preState
}
}
srote一般汇总所有reducer
import {createStore,applyMiddleware} from 'redux'
import countReducer from './count_reducer'
import thunk from 'redux-thunk'
export default createStore(countReducer,applyMiddleware(thunk))
ps:createStore第一个参数只接受一个reducer,所以要在reducers文件的index.js进行汇总,如下:
import {combineReducers} from 'redux'
import count from './count'
import persons from './person'
export default combineReducers({
count,
persons
})
使用
如在count组件中使用:
//引入store
import Store from '../../redux/store'
Store.getState('count')
3.react-redux
4.补充
reateStore,专门用于创建redux中最为核心的store对象 import {createStore,applyMiddleware} from ‘redux’ //引入为Count组件服务的reducer import countReducer from ‘./count_reducer’ //引入redux-thunk,用于支持异步action import thunk from ‘redux-thunk’
//暴露store export default createStore(countReducer,applyMiddleware(thunk))
**ps:createStore第一个参数只接受一个reducer,所以要在reducers文件的index.js进行汇总,如下:**
```js
/*
该文件用于汇总所有的reducer为一个总的reducer
*/
//引入combineReducers,用于汇总多个reducer
import {combineReducers} from 'redux'
//引入为Count组件服务的reducer
import count from './count'
//引入为Person组件服务的reducer
import persons from './person'
//汇总所有的reducer变为一个总的reducer
export default combineReducers({
count,
persons
})
使用
如在count组件中使用:
//引入store
import Store from '../../redux/store'
Store.getState('count')
3.react-redux
4.补充
|