React基础篇

769 阅读9分钟

React基础篇

一、setState

1.正确使用setState

由于数据是双向绑定的原因,当数据频繁的被操作 ,会触发dom页面不断的刷新,如果数据量大的话,还会导致的卡顿,对性能消耗比较大,所以React官方统一使用setState函数批量的更新数据

setState(partialState, callback)

  1. partialState: object| function

    用于产生于当前state合并的子集。

  2. callback: function

    state更新之后被调用

2. 关于setState()的三件事

不要直接修改State

例如,此代码不会重新渲染组件:

// 错误示范
this.state.comment = 'hello'

而是应该使用setState():

// 正确使用
this.setState({comment:'Hello'})
State的更新可能是异步的

出于性能考虑,React可能会把多个setState()调用合并成一个调用

import React, { Component } from 'react'
export default class SetStatePage extends Component {
   	constructor(props) {
        super(props);
        this.state = {
            counter: 0
        }
    }
    changeValue = v => {
        this.setState({
            counter: this.state.counter + v
        })
        console.log('counter', this.state.counter)
    }
    render() {
        const { counter } = this.state
        return ( 
        	<div>
            	<h3>SetStatePage</h3>
            	<button onClick={this.setCounter}>{counter}</button>
            </div>
        )
    }
}

如果要获取到最新状态值有以下方式:

  1. 在回调中获取状态值

    changeValue = v => {
        this.setState({
            counter: this.state.counter + v
        }, ()=>{
            console.log('counter', this.state.counter)
        })
    }
    
  2. 使用定时器

    setTimeout(()=>{
        this.changeValue();
    })
    
  3. 原生事件中修改状态

    componentDidMount() {
        docment.body.addEventLister('click', this.changeValue, false);
    }
    
总结: setState只有在合成事件和生命周期函数中是异步的,在原生事件和setTimeout中都是同步的,这里的异步其实是批量更新。
State的更新会被合并
changeValue = v => {
    this.setState({
        counter: this.state.counter + v
    })
}
setCounter = () => {
    this.changeValue(1);
    this.changeValue(2);
}

如果想要链式更新state

changeValue = v => {
	this.setState(state=>({counter: state.counter + v }));
}
setCounter = () => {
	this.changeValue(1);
	this.changeValue(2);
}

二、 组件生命周期

生命周期方法,用于在组件不同阶段执行自定义功能。在组件被创建插入到Dom时(即 挂载中阶段(mounting)),组件更新时,组件取消挂载或从DOM中删除时,都有可以使用的生命周期方法。

React16.3的生命周期.png

V16.4之后的生命周期:

1628399943022.png

V17可能会废弃的三个生命周期函数用getDerivedStateFromProps替代,目前使用的话加上UNSAFGE_:

  • componentWillMount
  • componentWilReceiveProps
  • componentWillUnUpdate

引入两个新的生命周期函数:

  • static getDerivedStateFromProps
  • getsnapshotBeforeUpdate

新引入的两个生命周期函数

getDerivedStateFromPropos
static getDerivedStateFromProps(nextProps, preState) 

getDerivedStateFromProps会在render之前调用,并且在初始挂载及后续更新时都会被调用。它应返回一个对象来更新state,如果返回null则不更新任何内容

请注意,不管原因时什么,都会在每次渲染前触发此方法。这次

UNSAFE_componentWillReceviProps形成对比,后者仅在父组件重新渲染时触发,而不是在内部调用setState时。

getSnapshotBeforeUpdate

getSnapshotBeforeUpdate(preProps, preveState)

在render之后,在componentDidUpdate之前。

getSnaopshotBeforeUpdate()在最新一次渲染输出(提交到Dom节点)之前调用。它使得组件能在发生更新之前从Dom中捕获一些信息(例如:滚动位置)。此生命周期的任何返回值将作为参数传递componentDidUpdate(preProps, prevState, snapshot)

三、Redux

reduxJavaScript应用的状态容器,提供可预测的状态管理。它保证程序行为一致性且易于测试。

redux.png

实操案例:

1. 需要一个store来存储数据
2. store里的reducer初始化state并定义state修改规则
3. 通过dispatch一个action来提交对数据的修改
4. action提交到reducer函数里,根据传入的action的type,返回新的state

创建store, src/store/ReaduxStore.js

import { createStore } from 'redux'
const counterReducer = (state=0, action) => {
    switch(action.type) {
        case 'ADD':
            return state + 1
        case 'MINUS':
            return state - 1
         default : 
            return state
    }
}
const store = createStore(counterReducer)
export default store

创建ReducPage

import React, { Component } from "react";
import store from "../store/ReduxStore";
export default class ReduxPage extends Component {
 componentDidMount() {
     store.subscribe(() => {
         console.log("subscribe");
         this.forceUpdate();
     });
 }
 add = () => {
 	store.dispatch({ type: "ADD" });
 };
 minus = () => { store.dispatch({ type: "MINUS" });
 render() {
     console.log("store", store);
     return (
         <div>
             <h3>ReduxPage</h3>
             <p>{store.getState()}</p>
             <button onClick={this.add}>add</button>
             <button onClick={this.minus}>minus</button>
         </div>);
     }
}

如果点击按钮不能更新,因为没有订阅(subscribe)状态变更

还可以在src/index.js的render里订阅状态变更

import store from './store/ReduxStore'
const render = ()=>{
    ReactDom.render(
    <App/>,
     document.querySelector('#root')
    )
}
render()
store.subscribe(render)

四、react-redux

react-redux是基于redux专门为react再次封装的存储器。

使用react-redux

  1. Provider 为后代组件提供store
  2. connect为组件提供数据和变更方法

全局提供store,index

src/index.js

import React from 'react'
import ReactDom from 'reacat-dom'
import App from './App'
import store from './store/'
import { Provider } from 'react-redux'

ReactDom.render(
    <Provider store = {store}>
    	<App/>
    <Provider>,
    docment.getElementById('root')
)

store

import { crateStore } from 'redux'

// 定义state初始化和修改规则,reduce 是一个纯函数

function counterReducer (state = 0, action) {
    switch(action.type) {
        case "ADD":
            return state + 1;
        case "MINUS":
            return state - 1;
        default: 
            return state;
    }
}

const store = createStore(counterReducer)

export default store;

App.js

import React from 'react'
import ReactReduxPage from './pages/ReactReduxPage'

export default function App() {
    return (
    <div>
        <ReacatReduxPage/>
    </div>
    )
}

ReactReduxPage.js

import React, { Component } from 'react';

export default connect(
  //mapStateToProps 把state映射到props
 state => ({num: state})
 ,   
  // mapDispatchToProps 把dispatch 映射到 props  默认会映射
  {
  	add:()=>({type:"ADD"}) 
  }
)( calss ReactReduxPage extends Componets {
    const { num, dispatch , add } = this.props;
    render() {
        return (
        	<div>
        		<h3> ReactReduxPage </h3>
           		<p>{num}</p>
                {/*<button onClick={ () => dispatch({ type:"ADD" })}>add</button>*/}
                <button onClick={ add }>add</button>
            </div>
        )
    }
})

五、react-router

前言

react-router包含3个库,react-router、react-router-dom和react-router-native。react-router提供最基本的路由功能,实际使用的时候我们不会直接安装react-router,而是根据应用运行环境选择安装react-router-dom(在浏览器使用)或者react-router-native在(rn中使用)。react-router-dom和react-router-native都依赖react-router,所以安装时,react-router也会自动安装,创建web应用

安装

npm i --save react-router-dom

案例

import React, { Component } from 'react'
import { BrowserRouter as Router, Route, NavLink, Switch } from 'react-router-dom'
class UserPage extends Component {
    render() {
        return (
         <h2>用户中心</h2>   
        )
    }
}
class home extends Component {
    render() {
        return (
         <h2>首页</h2>   
        )
    }
}
class EmptyPage extends Component {
    render() {
        return (
         <h2>404</h2>   
        )
    }
}
export default class homePage extends Component {
    render() {
        return (
            <div>
                <Router>
                    <NavLink to="/">首页</NavLink>
                    <NavLink to="/user-page">用户中心</NavLink>
            		<Switch>
                        <Route exact path="/user-page" component={UserPage} children={()=><div>children</div>} render={()=><div>render</div>} />
                        <Route exact path="/" component={home}/>
                        <Route component={EmptyPage}/>
                    </Switch>
                </Router>
            </div>
        )
    }
}

Route渲染内容的三种方式

Route渲染优先级: children > component > render

children: fuc

有时候,不管location是否匹配,你都需要渲染一些内容,这时候你可以用children。

除了不管location是否匹配都会被渲染之外,其它工作方法与render完全一样。

render: fuc

但是当你调用render的时候,你调用的只是函数。

只在当location匹配的时候渲染

component: component

只在当location匹配的时候渲染

404页面

设定一个没有path的路由在路由列表最后,表示一定匹配

   {/* 添加Switch表示仅匹配一个 */}	
   <Switch>
        {/* 根路由要添加exact,实现进准匹配 */}	
		<Route path="/" component={home}/>
        <Route exact path="/user-page" component={UserPage} />
        <Route component={EmptyPage}/>
   </Switch>

六、PureComponent

实现性能优化

定制了shouldComponentUpdata后的Component

import React, { Component, PureComponent } from 'react'

export default class PureComponentPage extends PureComponent {
    constructor (props) {
        super(props)
        this.state = {
            count: 0,
            obj: { num: 0 }
        }
    }
    setCount() {
        this.setState({
            count: 100,
            obj: {
                num: 100
            }
        })
    }
    render() {
        console.log('render')
        const { count }  = this.state
        return (
            <div>
                <h3>{count}</h3> 
                <button onClick={()=> {this.setCount()}}>改变</button>
            </div>
        )
    }
}

浅比较

缺点是必须要用class形式,而且要注意是浅比较

与Component 比较

React.PureComponentReact.component很相似。两者的区别在于React.Component并未实现shouldComponentUpdate(),而Rect.PureComponent中以浅层对比prop和state的方式来实现了该函数。

如果赋予React组件相同的props和state,render()函数会渲染相同的内容,那么在某些情况下使用React.PureComponent可提高性能。

注意

React.PureComponent中的shouldComponentUpdate()仅作对象的浅层比较。如果对象中包含复杂的数据结构,则有可能因为无法检查深层的差别,产生错误的比对结果。仅在你的props和state较为简单时,才使用React.PureComponent,或者在深层数据结构发生变化时调用forceUpdate()来确保组件被正确地更新。你也可以考虑使用immutable对象加速嵌套数据的比较。

此外,React.PureComponent中的shouldComponentUpdate()将跳过所有子组件树的prop更新。因此,请确保所有子组件也都是“纯”的组件

七、Hook

认识Hook

Hoook是什么? Hook 是一个特殊的函数,它可以让你"钩入"React的特性。例如,useState是允许你在React函数组件中添加state的Hook。

什么时候我会用Hook? 如果你在编写函数组件并意识到需要向其添加一些state,以前的做法是必须将其转化为class。现在你可以在现有的函数组件中使用Hook。

import React,{useState} from 'react'

export default function HookPage() {
    // 定义一个叫count的state变量,初始值为0
    const [count, setCount] = useState(0)
    return (
        <div>
            <h3>{count}</h3> 
            <button onClick={()=>setCount(count+1)}>添加</button>
        </div>
    )
}

使用Effect Hook

Effect Hook可以让你在函数组件中执行副作用操作。

数据获取,设置订阅以及手动更改React组件中的Dom都属于副作用。不管你知不知道这些操作,或是“副作用”这个名字,应该都在组件中使用过它们。

import React,{useState, useEffect} from 'react'

export default function HookPage() {
    const [count, setCount] = useState(0)
    useEffect(()=>{
        document.title = `You cliced ${count} times`
    })
    return (
        <div>
            <h3>{count}</h3> 
            <button onClick={()=>setCount(count+1)}>添加</button>
        </div>
    )
}

在函数组件主体内(这里指在React渲染阶段)改变DOM、添加订阅、设置定时器、记录日志以及执行其他包含副作用的操作都是不被允许的,因为这可能会产生莫名其妙的bug并破坏UI的一致性。

使用useEffect完成副作用操作。赋值给useEffect的函数会在组件渲染到屏幕之后执行。你可以把effect看作React的纯函数式世界通往命令式世界的逃生通道。

默认情况下,effect将在每轮渲染结束执行。但你可以选择让它在只有某些值改变的时候才执行。

effect的条件执行

默认情况下,effect会在每轮组件渲染完成执行。这样的话,一旦effect的依赖发生变化,它就会被重新创建。

然而,在某些场景下这么做可能会娇枉过去。比如,在上一章节的订阅实例中,我们不需要在每次组件更新时都创建新的订阅,而是仅需要在source props改变时重新创建。

要实现这一点,可以给useEffect传递第二个参数,它是effect所依赖的值数组。更新后的示例如下:

import React,{useState, useEffect} from 'react'

export default function HookPage() {
    const [count, setCount] = useState(0)
    const [date, setDate] = useState(new Date())
  
    useEffect(()=>{
        console.log('effect')
        document.title = `You cliced ${count} times`
    },[count])

    useEffect(()=>{
        const timer = setInterval(()=>{
            setDate(new Date())
         }, 1000)
    },[])
    return (
        <div>
            <h3>{count}</h3> 
            <h3>时间:{date.toLocaleTimeString()}</h3>
            <button onClick={()=>setCount(count+1)}>添加</button>
        </div>
    )
}

此时,只有当useEffect第二个参数数组里的数值改变后才会重新创建订阅。

清除effect

通常,组件卸载时需要清除effect创建的诸如订阅或计时器ID等资源。要实现这一点,useEffect函数需返回一个清除函数,以防止内存泄漏,清除函数会在组件卸载前执行。

   useEffect(()=>{
        const timer = setInterval(()=>{
            setDate(new Date())
         }, 1000)
        return ()=>clearInterval(timer)
    },[])

自定义Hook

有时候我们会想要在组件之间重用一些状态逻辑。目前为止,有两种主流方案来解决这个问题:高阶组件reader props 。自定义Hook可以让你在不增加组件的情况下达到同样的目的。

自定义Hook是一个函数,其名称以use开头,函数内部可以调用其他的Hook。

import React,{useState, useEffect} from 'react'

export default function HookPage() {
    const [count, setCount] = useState(0)
    useEffect(()=>{
        console.log('effect')
        document.title = `You cliced ${count} times`
    },[count])

    return (
        <div>
            <h3>{count}</h3> 
            <h3>时间:{useClock().toLocaleTimeString()}</h3>
            <button onClick={()=>setCount(count+1)}>添加</button>
        </div>
    )
}
const useClock = ()=> {
    const [date, setDate] = useState(new Date())
    useEffect(()=>{
        const timer = setInterval(()=>{
            setDate(new Date())
         }, 1000)
         return ()=> clearInterval(timer)
    }, [])
    return date
}

Hook使用规则

Hook就是Javascript函数,但是使用它们会有两个额外的规则:

  • 只能在函数最外层 调用Hook,不要在循环,条件判断或者子函数中调用。
  • 只能在React的函数组件 中调用Hook。不要再其他Javscript函数中调用。(还有一个地方可以调用Hook--就是自定义的Hook中。)

useMemo

把“创建”函数和依赖项数组作为参数传入useMemo,它仅会再某个依赖项改变时才会重新计算memoized值。这种优化有助于避免再每次渲染时都进行高开销的计算。

import React, {useState, useMemo} from 'react'

export default function useMemoPage() {
    const [count, setCount] = useState(0)
    
    const expensive = useMemo(() => {
        console.log('expensive')
        let sum = 0;
        for(let i=0;i<count;i++){
            sum+=i;
        }
        return sum
    }, [count])

    const [value, setValue] = useState('')
    return (
        <div>
            <h3>UseMemoPage</h3>
            <p>count: {count}</p>
            <p> expensive: {expensive} </p>
            <button onClick={()=>setCount(count+1)}>add</button>
            <input value={value} onInput={event => setValue(event.target.value)} ></input>
        </div>
    )
}

useCallback

把内联回调函数及依赖项数组作为参数传入useCallback, 它将返回该回调函数的memoized版本,该回调函数仅在某个依赖项改变时才会更新。当你把回调函数传递给经过优化的并使用引用相等性去避免非必要渲染(例如shouldComponentUpdate)的子组件时,它将非常有用。

import React, {useState, useCallback , PureComponent} from 'react'

export default function UseCallbackPage() {
    const [count, setCount] = useState(0)

    const addClick =useCallback(() => {
        let sum = 0;
        for(let i=0;i<count;i++){
            sum+=i;
        }
        return sum
    },[count]) 
    const [value, setValue] = useState('')
    return (
        <div>
            <h3>UseMemoPage</h3>
            <p>count: {count}</p>
            <button onClick={()=>setCount(count+1)}>add</button>
            <input value={value} onInput={event => setValue(event.target.value)} ></input>
            <Children addClick={addClick} ></Children>
        </div>
    )
}

class Children  extends PureComponent {
    render() { 
        const { addClick } = this.props
        console.log('Children render')
        return( <div>
            <button onClick={()=>addClick}>add</button>
       </div>)
    }
}