之前在react官网上囫囵吞枣学了react的一点基础知识,就急不可耐的写了很多代码(人脸识别,云笔记,实训的医院管理系统前端。。。),结果遇到了很多坑。react是一门很复杂的技术,而我缺乏系统的学习,正好看到一本《深入浅出react和redux》的电子书,借此深入学习react。
props和state
react天然具有高内聚,低耦合的特征(样式,内容,交互内封装一个组件中)。props
看作外部的接口,state
看作自己的内部状态,两者改变都会导致组件的重新渲染。react组件开发应合理划分组件的边界,将高内聚(交互频繁)的部分合并为一个组件,组件和组件中间保证低耦合(低信息交换)
props类型约束与初始化
假设一个组件名字是Counter(计数器),有string类型的caption
,number类型的initValue
,其中caption
必须被申明,且必须是string类型,initValue
必须是number类型,需要有默认值。
class Counter extends React.Component { |
如果违背了约束条件,则会在console中产生warning,有效避免了调试找bug的困难。
显式调用setState
如果直接对state.variable
操作,不会重新渲染组件,但也会影响到state
,并报warning,而setState()
不仅会改变state
的状态,还会驱动组件的重新渲染。
react组件类的装载过程
constructor()
->getInitialState()
->getDefaultProps()
->componentWillMount()
->render()
->componentDidMount()
constructor
class Counter extends React.Component { |
构造函数一般有两个目的:
- 初始化
state
,没有状态的组件不需要state
,也不需要重载构造函数,而有状态的组件,最好在构造函数中统一对state
初始化 - 绑定
this
,因为ES6下类的成员函数在执行时的this
不是和类实例自动绑定的,构造函数中,this
就是当前组建实例,所以为了方便将来的调用,往往在构造函数中对实例的函数绑定this
为当前实例。
getInitialState/getDefaultProps
这是一种已经废弃的写法,只会执行一次,用于在使用React.createClass()
时候初始化state
和props
。因为这种创建class的方法已经被废弃,所以这种方法也不会再使用。
const Counter = React.createClass({ |
这是等价的老式写法。
render
render()
是一个无副作用的纯函数,用于返回jsx,没有默认实现,且必须被实现,如果不想渲染任何DOM,可以返回null
。因为无副作用,所以不能在这个函数中调用setState()
componentWillMount/componentDidMount
装载过程中,componentWillMount()
会在调用render()
函数之前被调用,componentDidMount()
会在调用render()
函数之后被调用,这两个函数就像是render()
函数的前哨和后卫,一前一后,把render()
函数夹住,正好分别做render
前后必要的工作。
由于componentWillMount()
执行时render()
已经执行,所以调用setState()
也没有意义,需要预处理的东西可以放到constructor()
中,所以这个函数没有什么用。
render()
只是个返回jsx对象的函数,不执行之际的渲染,所以render()
执行后不会立刻执行该组件的componentDidMount()
,等同级的组件render()
都执行完了,react会分析DOM的改变,执行重新渲染,完毕后,才会依次执行componentDidMount()
。
因为这个方法执行的时候,DOM已经渲染完毕所以可以做两件事:
- 此时可以进行和服务器的通信(ajax请求)。
- 此时可以使用其他js库(jquery)
componentWillMount()
可以在服务器端被调用,也可以在浏览器端被调用;而componentDidMount()
只能在浏览器端被调用,在服务器端使用React的时候不会被调用。
react组件类的更新过程
当props
或state
更新时,就会触发react的更新过程。
componentWillReceiveProps()
->shouldComponentUpdate()
->componentWillUpdate()
->render()
->componentDidUpdate()
componentWillReceiveProps(nextProps)
只要是父组件的 render()
函数被调用,在render()
函数里面被渲染的子组件就会经历更新过程。通过this.setState()
方法触发的更新过程不会调用这个函数(因为这个函数适合根据新的props
值,也就是参数nextProps
,来计算出是不是要更新内部状态state
,父的props
改变会触发子的state
的隐式改变,而这会产生死循环)
每个React组件都可以通过this.forceUpdate()
强行引发一次重新绘制。
shouldComponentUpdate(nextProps,nextState)
render()
函数决定了该渲染什么,而shouldComponentUpdate()
决定了一个组件什么时候不需要渲染。这两个函数是唯二需要返回值的react生命周期函数。shouldComponentUpdate()
返回boolean类型决定是否需要更新。这个函数和react性能息息相关,默认返回true,每次要重新渲染,但是想提高性能,则需要定制这个函数。shouldComponentUpdate(nextProps, nextState) {
return (nextProps.caption !== this.props.caption) ||(nextState.count !== this.state.count);
}
现在,只有当caption
改变,或者state
中的count
值改变,shouldComponent
才会返回true。
如果组件的shouldComponentUpdate()
函数返回true,React接下来就会依次
调用对应组件的componentWillUpdate()
,render()
和componentDidUpdate()
函数。
和装载过程不同的是,当在服务器端使用React渲染时,这一对函数中的Did函数,也就是componentDidUpdate()
,并不是只在浏览器端才执行的,无论更新过程发生在服务器端还是浏览器端,该函数都会被调用。
当React组件被更新时,原有的内容被重新绘制,这时候就需要在componentDidUpdate()
再次调用jQuery代码。
react组件类的卸载过程
componentWillUnmount
React组件的卸载过程只涉及一个函数componentWillUnmount()
,当React组件要从DOM树上删除掉之前,对应的componentWillUnmount()
函数会被调用,所以这个函数适合做一些清理性的工作。一般要把componentWillMount()
中创建的一些DOM元素处理掉,防止内存泄露。
组件通信
父组件改变子组件的状态,通过绑定子组件的props
在自己的state
上,改变自己的state
。
子组件向父组件传递信息,子组件调用父组件绑在其props
上的父方法:class Counter extends React.Component {
//...
onUpdate = (newValue)=> {
this.props.onUpdate(newValue)
}
}
class CounterPanel extends React.Component {
//...
render = ()=>{
return (
<div>
<Counter onUpdate={this.onUpdate} caption="first"/>
<Counter onUpdate={this.onUpdate} caption="second"/>
<Counter onUpdate={this.onUpdate} caption="third"/>
</div>
)
}
onUpdate = (newValue){
//收到子组件的调用
}
}
父组件向子组件传递信息,调用子组件的方法,通过绑定rel:class Counter extends React.Component {
//...
constructor(props)=>{
super(props)
this.props.bind(this)//绑定子组件的引用到父组件的成员变量中
}
invokedMethod() {
//...子组件 被调用的方法
}
}
class CounterPanel extends React.Component {
//...
counter1 = null;
render = ()=>{
return (
<div>
<Counter bind={(rel)=>{this.counter1 = rel}}/>
</div>
)
}
invokerMethod = ()=>{
if(this.counter1) {
this.counter1.invokedMethod()//父组件通过子组件的句柄调用子组件
}
}
}
同级组件通信可以通过公共父组件通信(状态提升)。
当组件都有state
,会造成数据的冗余,例如三个计数器Counter各自保有num
,CounterPanel有三者的num
的和,一旦有个组建有bug,就会出现数据的不一致。所以,需要有一个领头羊作为标准,其他的组件和他保持一致(不保有state
)。为了解决这种“多个领头羊”的问题,把数据放到全局空间统一管理,这就是redux和flux的store的概念。
同时多层组件要通信则需要借助中间层传递props
,这造成了数据的冗余,违反了低耦合的要求。
单向数据流框架的始祖Flux和一个更强实现Redux
Redux是管理应用状态的框架,
////////////////
redux:
store只能有一个,存状态
reducer必须是纯函数,深拷贝,不能执行ajax
ActionType单独管理Action的类型
ActionCreator(工厂模式)单独管理Action
XXX中import XXXUI 写事件
XXXUI中export const 直接返回一个无状态的render函数:(props)=>(<>..<>),便于ui
逻辑分离,而且无状态组件性能更优
redux中间件,和redux、 devtools同步使用(增强函数)
redux-thunk
redux-saga
react-redux:
react-redux!=redux,redux是一个相对独立的数据管理框架,redux和react-redux是两个独立的npm包,react-redux要依赖redux,使用时两个都需要手动安装?yarn似乎可以直接关联依赖。
import {Provider , connect} from ‘react-redux’
提供器,包裹要使用的组件;连接器,需要定义状态影射函数stateToProps,可以将UI模块的全部state改变为props,从而去状态化,定义事件影射函数dispatchToProps,不需要再组建内bind方法的this,所有的事件为OnXXX = this.props.xxx。最后export default connect(stateToProps,dispatchToProps)(XXUI)
好习惯:
解构函数和属性数据
在render的第一行
const {xxx} = this.props;//方法和数据
react router
react hooks
ReactRouter
|
路由可以嵌套多层,但是子路由需要完整路径
精确匹配:<Route exact path="/" component={Page}/>
动态传值:<Route path="/page/:id" component={Page}/>
,Page中let id = this.props.match.params.id
获取id
重定向:
标签式:import { Redirect} from 'react-router-dom';
render() {
return (<Redirect to="/page1"/>)
}
编程式:this.props.history.push("/page1")
动态路由:let routeConfig = [
{path:"/page1",title:"page1",exact:true,component:Page1},
{path:"/page2",title:"page2",exact:true,component:Page2},
{path:"/page3",title:"page3",exact:true,component:Page3},
]
依据数据生成Router标签
React-hooks
useState
import React from 'react' |
import React,{ useState } from 'react' |
hooks使用了useState,定义了一个count和setCount方法,提供了一个getter和setter。于是可以避免写class,直接用function就可以定义一个有状态的组件。
useState必须顺序申明,不能存在于条件判断句子中。
useEffect
useEffect替代了生命周期函数:import React,{ useState, useEffect } from 'react'
function Example() {
//...
useEffect(()=>{
console.log('componentDidMount')
return ()=>{
console.log('componentWillUmount')
}
},[count])
//...
}
第二个参数,监听数据的状态改变,当数组中的数据改变,才执行return返回的解绑前函数,当数组为[]
,则当组件卸载时,才执行解绑函数。
useContext
解决父子组件传值的问题import React,{ useContext, createContext, useState} from 'react'
const CounterContext = createContext({})
function Counter() {
let count = useContext(CounterContext)
return <p>{count}</p>
}
function Example() {
const [count,setCount] = useState(0)
return (
<div>
<p>Count:{count}</p>
<button onClick={()=>{setCount(count+1)}}>click</button>
<CountContext.Provider value={count}>
<Counter>
</CounterProvider.Provider>
</div>
)
}
useReducer
import React,{ useReducer, useState} from 'react' |
和useContext使用,达到代替redux的效果
import React,{ useContext, createContext, useState} from 'react' |
useMemo
useMemo解决hooks性能问题,解决生命周期中shouldComponentUpdate的更新问题。
useRef
useRef用于获取DOM和保存变量