0%

对React学习的一点深入

之前在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 {
//.... Counter组建的定义
}
/* Counter的props类型约束 */
Counter.propTypes = {
caption: PropTypes.string.isRequired,
initValue: PropTypes.number,
onUpdate: PropTypes.func
}
/* Counter的props初始化 */
Counter.defaultProps = {
initValue: 0 //如果没有声明,初始化为0
}

如果违背了约束条件,则会在console中产生warning,有效避免了调试找bug的困难。

显式调用setState

如果直接对state.variable操作,不会重新渲染组件,但也会影响到state,并报warning,而setState()不仅会改变state的状态,还会驱动组件的重新渲染。

react组件类的装载过程

constructor()->getInitialState()->getDefaultProps()->componentWillMount()->render()->componentDidMount()

constructor

class Counter extends React.Component {
constructor(props) {
super(props) //如果有构造函数,第一行一定要是调用超类方法,否则无法访问props
this.state.count = props.initValue
this.onClickIncreaseButton = this.onClickIncreaseButton.bind(this)
/*
bind操作符,另一种NB的写法:
this.onClickIncreaseButton = ::this.onClickIncreaseButton
*/
}
}

构造函数一般有两个目的:

  • 初始化state,没有状态的组件不需要state,也不需要重载构造函数,而有状态的组件,最好在构造函数中统一对state初始化
  • 绑定this,因为ES6下类的成员函数在执行时的this不是和类实例自动绑定的,构造函数中,this就是当前组建实例,所以为了方便将来的调用,往往在构造函数中对实例的函数绑定this为当前实例。

getInitialState/getDefaultProps

这是一种已经废弃的写法,只会执行一次,用于在使用React.createClass()时候初始化stateprops。因为这种创建class的方法已经被废弃,所以这种方法也不会再使用。

const Counter = React.createClass({
getInitialState: function() {
return {count: 0};
},
getDefaultProps: function() {
return {initValue: 0}
})
});

这是等价的老式写法。

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组件类的更新过程

propsstate更新时,就会触发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

  
import React from 'react';
import { BrowserRouter, Route, Link} from 'react-router-dom';
import {Page1,Page2,Page3} from '../page/''

class GlobalRouter extends React.Component {
render() {
return (
<BrowserRouter>
<ul>
<li><Link to="/page1">Page1</Link></li>
<li><Link to="/page2">Page2</Link></li>
<li><Link to="/page3">Page3</Link></li>
</ul>

<Route exact path="/page1" component={Page1}/>
<Route path="/page2" component={Page2}/>
<Route path="/page3" component={Page3}/>
</BrowserRouter>);
}
}

export default GlobalRouter;

路由可以嵌套多层,但是子路由需要完整路径

精确匹配:<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'
class Example extends React.Component {
constructor(props) {
super(props)
this.state = {count:0}
this.addCount = this.addCount.bind(this)
}

render() {
return (
<div>
<p>Count:{this.state.count}</p>
<button onClick={this.addCount}>click</button>
</div>
)
}

addCount() {
this.setState({count:this.state.count+1})
}
}
import React,{ useState } from 'react'
function Example() {
const [count,setCount] = useState(0)
return (
<div>
<p>Count:{count}</p>
<button onClick={()=>{setCount(count+1)}}>click</button>
</div>
)
}

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'


function Example() {
const [count,dispatch] = useReducer((state,action)=>{
switch(action.type) {
case 'add':
return state+1
case 'sub':
return state-1
default:
return state
}
},0)

return (
<div>
<h1>now count is:{count}</h1>
<button onClick={()=>{dispatch('add'}}>increase</button>
<button onClick={()=>{dispatch('sub'}}>decrease</button>
</div>
)
}

和useContext使用,达到代替redux的效果

import React,{ useContext, createContext, useState} from 'react'

const ColorContext = createContext({})

const reducer = (state,action)=>{
switch(action.type){
case 'update_color':
return action.color;
default:
return state;
}
}

function Color(props) {
const [color,dispatch] = useReducer(reducer,'blue')
return (
<ColorContext.Provider value={{color:'blue'}}>
{props.children}
</ColorProvider.Provider>
)
}

function ShowArea() {
const {color} = useContext(ColorContext)
return <div style={{color:color}}>font color is {color}</div>
}

function Buttons() {
const {dispatch} = useContext(ColorContext)
return
(<div>
<button onClick={()=>{dispatch(type:'update_color',color:'red')}>red</button>
<button onClick={()=>{dispatch(type:'update_color',color:'yellow')}>yellow</button>
</>)
}

function Example() {
return (
<Color>
<ShowArea/>
<Buttons/>
</Color>
)
}

useMemo

useMemo解决hooks性能问题,解决生命周期中shouldComponentUpdate的更新问题。

useRef

useRef用于获取DOM和保存变量

自定义hooks函数



Disqus评论区没有正常加载,请使用科学上网