React

react筆記

腳手架環(huán)境:全局安裝 npm i create-react-app -g

進(jìn)入目錄create-react-app 項(xiàng)目名稱

create-react-app --version

jsx原理

跟vue一樣,只能寫(xiě)在一個(gè)標(biāo)簽里類似vue用template

//jsx原理是個(gè)語(yǔ)法糖,利用createElement創(chuàng)建節(jié)點(diǎn),節(jié)點(diǎn)名稱,屬性,內(nèi)容
var a = React.createElement('h2',null,'hello react')
ReactDOM.render(a,document.getElementById('box'))

var a = <h2>hello react</h2>
ReactDOM.render(a,document.getElementById('box'))

在react里寫(xiě)類名用className=""

        function fn() {
            var time = Date.now()
            ReactDOM.render(<div className="oon">
                                {new Date(time).toLocaleString()}                
    </div>,
                            document.getElementById('box'));
        }
        setInterval(() => {
            fn()
        }, 1000);

在react里,{插值表達(dá)式} 里不能直接渲染對(duì)象,只能直接渲染數(shù)組

但是渲染數(shù)組時(shí),必須要給每個(gè)元素指定給一個(gè)key值 (保證唯一性)

遍歷對(duì)象

把對(duì)象轉(zhuǎn)換為數(shù)組

Object.keys(對(duì)象) 返回對(duì)象的所有的鍵構(gòu)成的數(shù)組

Object.value(對(duì)象) 返回對(duì)象的所有的值構(gòu)成的數(shù)組

Object.entries(對(duì)象) 返回對(duì)象所有的鍵值對(duì),所構(gòu)成的數(shù)組

var obj={a:1,b:2,c:3,d:4,e:5} 
ReactDOM.render(<div>
                <ul>
                {
    Object.keys(obj).map((item)=>{
    return <li key={item}>{item}:{obj[item]}</li>
})
}
    </ul>
</div>,
document.querySelector("#box"));

腳手架環(huán)境引用圖片

方法1:
import 變量 from "圖片路徑"


方法2:
<img src={require("圖片的路徑")} />

驗(yàn)證屬性

下載插件npm install --save pro-types 不用下載安裝就可以用

父組件內(nèi)容同上,只是把n傳過(guò)來(lái)了

//子組件 引用import PropTypes from 'prop-types';
import PropTypes from 'prop-types';  //驗(yàn)證屬性的包

// 驗(yàn)證屬性:n必須是number,而且必須傳
static propTypes = {
    n: PropTypes.number.isRequired
}
static propTypes ={
  key:PropTypes.類型.[isRequired]
}

基本用法

//todolist.js
import React, { Component,Fragment } from 'react'
import Todoitem from '../todoitem'

export default class todolist extends Component {
    constructor(props) {
        super()
        this.state = {
            listData:[]
        }
    }
    render() {
        return (
            //最外層跟vue一樣必須只能有一個(gè)元素,但是如果不需要這個(gè)元素可以直接用<></>代替最外層
            //或者用Fragment
            <>
                <ul>
                    {
                        //循環(huán)必須要加key值
                        this.state.listData.map((item,index)=>{
                            //父?jìng)髯訑?shù)據(jù)singleItem
                            //父?jìng)髯臃椒╣etdata,deldata
                            return <Todoitem key={index} singleItem={item} getdata={this.changeState.bind(this)} deldata={this.delState.bind(this)}/>
                        })
                    }
                </ul>
            </>
        )
    }
    componentDidMount() {
        this.iniData()
    }
    changeState(id){
        let arr = [...this.state.listData]
        arr.map(item=>{
            item.id == id ? item.isComplete = !item.isComplete : console.log(item)
        })
        console.log(arr)
        this.setState({
            listData : arr
        })
    }
    delState(id){
        //深復(fù)制方便修改數(shù)據(jù)
        //展開(kāi)運(yùn)算符能深復(fù)制2層以內(nèi)的數(shù)據(jù),復(fù)雜的數(shù)據(jù)結(jié)構(gòu)可以用immutable擴(kuò)展包
        let arr = [...this.state.listData]
        let newarr = arr.filter(item=>{
            return item.id!=id
        })
        //react無(wú)法直接修改state里面的數(shù)據(jù)
        //只能通過(guò)setState修改
        this.setState({
            listData : newarr
        })
    }
    async iniData(){
        await fetch('http://rap2api.taobao.org/app/mock/data/1877922')
                .then(resp => resp.json())
                .then(json => {
                    this.setState({
                        listData:json.lists
                    })
                })
        console.log(this.state.listData)
    }
}

//Todoitem.js
import React, { Component } from 'react'
export default class todoitem extends Component {
    render() {
        return (
            <div>
                {/* this.props.singleItem為父組件傳過(guò)來(lái)的數(shù)據(jù) */}
                <div onClick={()=>this.changeState(this.props.singleItem.id)}>{this.props.singleItem.name}-{this.props.singleItem.isComplete ? '已完成' : '未完成'}</div>
                <button onClick={()=>{this.delData(this.props.singleItem.id)}}>Del</button>
                <div  style={{display: true ? "block" : "none"}} ref="getref" onClick={this.handleGetRef}>動(dòng)態(tài)綁定style</div>
                <input type="text" ref={(node)=>this.username=node} />

            </div> 
        )
    }
    delData=(id)=>{
        //直接調(diào)用父組件的方法
        this.props.deldata(id)
    }
    changeState=(id)=>{
        this.props.getdata(id)
    }
    //通過(guò)ref拿到當(dāng)前元素
    handleGetRef=()=>{
        //方法1
        console.log(this.refs.getref)
        //方法2
        //ref={(當(dāng)前節(jié)點(diǎn)的變量)=>this.當(dāng)前實(shí)例的變量=當(dāng)前節(jié)點(diǎn)的變量}
        //通過(guò)this.當(dāng)前實(shí)例變量就能找到,例:this.username.value
        //this.當(dāng)前實(shí)例的變量  就找到了這個(gè)結(jié)節(jié)
        var username = this.username.value;
    }
}

生命周期

舊生命周期

初始:

componentWillMount(服務(wù)端渲染會(huì)重復(fù)執(zhí)行有隱患):render之前最后一次修改狀態(tài)機(jī)會(huì)

render:只能訪問(wèn)props,state,不允許修改state

componentDidMount(一般在這里執(zhí)行ajax,綁定setInterval):Dom渲染完成過(guò)后

constructor:constructor()中完成了React數(shù)據(jù)的初始化,它接受兩個(gè)參數(shù):props和context,當(dāng)想在函數(shù)內(nèi)部使用這兩個(gè)參數(shù)時(shí),需使用super()傳入這兩個(gè)參數(shù)。
注意:只要使用了constructor()就必須寫(xiě)super(),否則會(huì)導(dǎo)致this指向錯(cuò)誤。

更新:

componentWillReceiveProps:父組件修改屬性觸發(fā)

shouldComponentUpdate(對(duì)比state,無(wú)變化則組織render更新):返回false會(huì)組織render 調(diào)用,性能調(diào)優(yōu)函數(shù),可以獲取新老狀態(tài)

shouldComponentUpdate(nextProps,nextState){  if(this.state.myname!== nextProps.myname){    return true  }  return false}

render:~

componeDidUpdate:可以修改Dom

銷毀:

componentWillUnMount(在這里清除定時(shí)器):組件銷毀前觸發(fā)

新生命周期

getDerivedStateFromProps:第一次初始化組件以及后續(xù)的更新過(guò)程中(包括自身狀態(tài)更新以及父?jìng)髯樱祷匾粋€(gè)對(duì)象作為新的state,返回null則說(shuō)明不需要在這里更新state

初始化會(huì)執(zhí)行,每次更新也會(huì)執(zhí)行

PS:無(wú)法做異步請(qǐng)求,要做異步必須配合componeDidUpdate結(jié)合state來(lái)發(fā)ajax請(qǐng)求

static getDerivedStateFromProps(nextProps, prevState) {    //這里可解決頻繁發(fā)送請(qǐng)求的問(wèn)題,多次請(qǐng)求只會(huì)拋?zhàn)詈笠淮握?qǐng)求  if (nextProps.isLogin !== prevState.isLogin) {    return {      isLogin: nextProps.isLogin,    };  }  return null;}componentDidUpdate(prevProps, prevState) {  if (!prevState.isLogin && this.props.isLogin) {    this.handleClose();  }}

getSnapshotBeforeUpdate(prevProps, prevState)---解決拿到狀態(tài)時(shí)間過(guò)久的問(wèn)題

getSnapshotBeforeUpdate(){  //獲取滾動(dòng)條位置  return{    y:100  }}componentDidUpdate(preProps,preState,data){  //data為getSnapshotBeforeUpdate返回的數(shù)據(jù),{y:100}}

代替componentWillUpdate。
常見(jiàn)的 componentWillUpdate 的用例是在組件更新前,讀取當(dāng)前某個(gè) DOM 元素的狀態(tài),并在 componentDidUpdate 中進(jìn)行相應(yīng)的處理。
這兩者的區(qū)別在于:

  1. 在 React 開(kāi)啟異步渲染模式后,在 render 階段讀取到的 DOM 元素狀態(tài)并不總是和 commit 階段相同,這就導(dǎo)致在
    componentDidUpdate 中使用 componentWillUpdate 中讀取到的 DOM 元素狀態(tài)是不安全的,因?yàn)檫@時(shí)的值很有可能已經(jīng)失效了。
  2. getSnapshotBeforeUpdate 會(huì)在最終的 render 之前被調(diào)用,也就是說(shuō)在 getSnapshotBeforeUpdate 中讀取到的 DOM 元素狀態(tài)是可以保證與 componentDidUpdate 中一致的。
    此生命周期返回的任何值都將作為參數(shù)傳遞給componentDidUpdate()。

pureComponent可以解決組件重復(fù)更新的問(wèn)題

import React,{PureComponent} from 'react'
export default class App extends PureComponent {
  //原理是scu內(nèi)部一直在對(duì)比(shallow equal,return ture)
}

用于優(yōu)化頁(yè)面性能:

shouldComponentUpdate(nextProps,nextState){} 第一個(gè)參數(shù)是最新的接收的數(shù)據(jù),第二個(gè)參數(shù)是最新的數(shù)據(jù)

返回布爾值 :true視圖就會(huì)render,false視圖不會(huì)渲染

可以減少不必要的渲染,增加頁(yè)面的性能

export default class Item extends Component {
    shouldComponentUpdate(nextProps,nextState){
        //判斷,當(dāng)里面的數(shù)據(jù)和新改變的數(shù)據(jù)不相等時(shí)才渲染render
        return this.props.completed !== nextProps.completed; 
    }
    render(){
        console.log("item render");
        let {completed,id,task,changeComplete,remove} = this.props;
        return <li>
                    <input type="checkbox" defaultChecked={completed} onChange={changeComplete.bind(this,id)}/>
                                    {task}
                     <button onClick={remove.bind(this,id)}>刪除</button> 
              </li>
    }
}

componentDidMount(第一個(gè)參數(shù)是上一次的state數(shù)據(jù),) 類似vue里的mounted ,在組件掛在后....獲取節(jié)點(diǎn)/發(fā)送請(qǐng)求等 這個(gè)鉤子能放訪問(wèn)this,所以在這調(diào)用方法時(shí),不用再綁定this了

render 渲染界面

static getDerivedStateFromProps(props,state) 更新派生屬性:第一個(gè)參數(shù)是props最新數(shù)據(jù),此方法不能用this

 派生狀態(tài):當(dāng)組件的state的值 如果 來(lái)自于props 就叫派生狀態(tài)
(獲取派生狀態(tài)來(lái)自于props),加了static就是靜態(tài)方法,
在面向?qū)ο罄?類不用實(shí)例化就可以直接使用這個(gè)類的方法,例:Date.now()
靜態(tài)方法里不能用this,因?yàn)闆](méi)有實(shí)例化
static getDerivedStateFromProps(props, state) {  //更新派生狀態(tài)
    return {
        a:props.n
    }
}

當(dāng)組件實(shí)例被創(chuàng)建并插入DOM中時(shí)(掛載階段),生命周期的鉤子函數(shù)執(zhí)行順序:

constructor()
static getDerivedStateFromProps() 
render()
componentDidMount() //永遠(yuǎn)在最后執(zhí)行
one 組件是test的子組件
test constructor
test.js

test render
one.js

one constructor
one.js

one render
one.js

one componentDidMount
test componentDidMount

更新階段的鉤子函數(shù)

getSnapshotBeforeUpdate(prevProps,prevState) 更新之前,返回當(dāng)前dom狀態(tài)給componentDidUpdate的第三個(gè)參數(shù),如果用了此鉤子函數(shù)那么必須寫(xiě)下面的鉤子函數(shù)

componentDidUpdate(prevProps,prevState,snapshot) 更新發(fā)生后立即調(diào)用。初始渲染不會(huì)調(diào)用此方法。監(jiān)聽(tīng)數(shù)據(jù)的變化

卸載

componentWillUnmount()從DOM中刪除組件時(shí)調(diào)用此方法,//清理資源,防止內(nèi)存泄露

經(jīng)常出現(xiàn)一個(gè)bug,異步請(qǐng)求后,卸載組件,組件卸載結(jié)束后異步數(shù)據(jù)接收過(guò)來(lái)時(shí)

index.js:1375 Warning: Can't perform a React state update on an unmounted component. This is a no-op, but it indicates a memory leak in your application. To fix, cancel all subscriptions and asynchronous tasks in the componentWillUnmount method.
    in Two (at App.js:14)
    in div (at App.js:13)
    in App (at src/index.js:7)

然后在componentWillUnmount里面寫(xiě)

//重寫(xiě)setState    
this.setState = (state,callback)=>{ return;};

路由

需要下載插件 npm install react-router-dom

分為history模式 BrowserRouter ==>URL不加#

history這個(gè)模式需要后端配置好才能用

hash 模式 HashRouter ===>URL加#

現(xiàn)在react-router-dom版本6.0變化

  • Switch -》Routes
  • component={Login} -》 element={<Login />}
//route/index.js
import {
    HashRouter as Router,
    Redirect,
    Route,
    Switch
} from 'react-router-dom'
//Login Dashboard NotFound為組件
import NotFound from '../error'
import Login from '../pages/login'
import Dashboard from '../pages/dashboard'



const BlogRouter = ()=> {
    return <Router>
        <Switch>
            {/*Redirect為重定向,to為定的路由路徑*/}
            <Route path='/login' component={Login} />
            {
                localStorage.getItem('token') ?
                <Route path='/' component={Dashboard} /> :
                <Redirect to="/login" />
            }
            {/*exact表示精準(zhǔn)定位*/}
            <Redirect from="/" to="/home" exact/>
            {/*路由的匹配方式為從上往下匹配,匹配到相應(yīng)的path就會(huì)直接跳轉(zhuǎn),防止路徑出錯(cuò),需要用*來(lái)匹配所有路徑引導(dǎo)至404資源丟失頁(yè)面*/}
            <Route path="*" component ={NotFound}/>
        </Switch>
    </Router>
}
export default BlogRouter

路由傳參

路由三大對(duì)象

  • history -- 路由跳轉(zhuǎn)掉用
  • location -- 讀取query傳的參數(shù)
  • match -- 讀取Url地址參數(shù)

1.params

<Route path='/path/:name' component={Path}/>
<NavLink to="/path/2">xxx</NavLink>
//如果要傳多個(gè)就繼續(xù)/:id/:xx...
this.props.history.push({pathname:"/path/" + name});
讀取參數(shù)用:this.props.match.params.name

優(yōu)勢(shì) : 刷新地址欄,參數(shù)依然存在
缺點(diǎn):只能傳字符串,并且,如果傳的值太多的話,url會(huì)變得長(zhǎng)而丑陋。

2.query

<Route path='/query' component={Query}/>
<NavLink to={{ path : ' /query' , query : { name : 'sunny' }}}/>
this.props.history.push({pathname:"/query",query: { name : 'sunny' }});
讀取參數(shù)用: this.props.location.query.name

優(yōu)勢(shì):傳參優(yōu)雅,傳遞參數(shù)可傳對(duì)象;
缺點(diǎn):刷新地址欄,參數(shù)丟失

3.state

<Route path='/sort ' component={Sort}/>
<NavLink to={{ path : ' /sort ' , state : { name : 'sunny' }}}/> 
this.props.history.push({pathname:"/sort ",state : { name : 'sunny' }});
讀取參數(shù)用: this.props.location.query.state 

優(yōu)缺點(diǎn)同query

4.search

<Route path='/web/departManange ' component={DepartManange}/>
<NavLink to="web/departManange?tenantId=12121212">xxx</NavLink>
this.props.history.push({pathname:"/web/departManange?tenantId" + row.tenantId});
讀取參數(shù)用: this.props.location.search

優(yōu)缺點(diǎn)同params

路由高階組件withrouter

由于子組件沒(méi)有history方法,在里面獲取不到props參數(shù)

//這里當(dāng)做props傳入
<SideMenu collapsed={this.state.collapsed} airhistory={this.props.history}/>

第二種方法用withrouter高階組件傳入history方法

//高階組件withRouter 獲取低階組件,生成高階組件
import { withRouter } from 'react-router' //路由
class topHeader extends Component {
    rednder(){
        return {
            this.props.collapsed
        }
    }
    quiet=()=>{
        this.props.history.push('/login')
    }
}
export default withRouter(topHeader)

權(quán)限路由

//App.js
import React from 'react';
import './App.css';
import {Route,Redirect,Switch} from 'react-router-dom'
import {subRoutes} from './router' //引入subroutes數(shù)據(jù)
import Admin from './components/layout'; //引入布局
import Myroute from './Myroute'
function App() {
  return (
    <div className="App">
       <Admin>
          <Switch>
          {
              subRoutes.map((item)=>{
      //當(dāng)進(jìn)入頁(yè)面時(shí),跳轉(zhuǎn)到自定義高階組件Myroute中進(jìn)行操作
                return <Myroute path={item.path} key={item.path} component={item.component} roles={item.roles} />
              })
          }
          <Redirect from="/home" to="/home/list" exact />
          </Switch>
       </Admin>
    </div>
  );
}

export default App;
//myroute.js
import React, { Component } from 'react'
import {Route,Redirect} from 'react-router-dom'
export default class Myroute extends Component {
    render() {
        let {path,component:Com,roles} = this.props
        // 當(dāng)進(jìn)入一個(gè)頁(yè)面時(shí),進(jìn)行遍歷當(dāng)前的用戶是否在router里的權(quán)限數(shù)組里,如果有就true,否則為false
        var hasPermission = roles.some((item)=>item===sessionStorage.getItem('user')) 
        return <Route path={path} render={
            (props)=>{
                // 如果為true則渲染當(dāng)前頁(yè)面,如果為false則彈出沒(méi)有權(quán)限
                return hasPermission ? <Com {...props} /> :<div>沒(méi)有權(quán)限訪問(wèn)該頁(yè)面</div>
            }
        } />
    }
}
**router/index.js
export const subRoutes =[
    {
        path:'/home/dashboard',
        component:DashBoard,
        roles:['zhm','abc']
    },
    {
        path:'/home/list',
        component:List,
        roles:['zhm','abc']
    },
    {
        path:'/home/setting',
        component:Setting,
        roles:['zhm']

    },
    {
        path:"/home/add",
        component:Add,
        roles:['zhm','abc']
    },{
        path:'/home/notify',
        component:Notify,
        roles:['zhm','abc']
    }
]

redux

狀態(tài)管理工具,各個(gè)組件可以訪問(wèn)公共數(shù)據(jù)

各個(gè)組件都需要用的信息都可以存到redux里,類似vuex

npm install redux

單向數(shù)據(jù)流

store 倉(cāng)庫(kù)是唯一的

atate 是只讀的,修改需要拷貝副本

使用純函數(shù)來(lái)執(zhí)行修改

store文件夾

//index.js
import {createStore} from "redux";
import reducer from './reducer';  //專門用于修改狀態(tài)的純函數(shù)
var store = createStore(reducer); // 傳入ruducer 創(chuàng)建一個(gè)store

export default store;
//reducer.js
//初始數(shù)據(jù)對(duì)象
var initState ={ 
    n:1
}
//創(chuàng)建一個(gè)純函數(shù),有兩個(gè)參數(shù),第一個(gè)為數(shù)據(jù),第二個(gè)為加工數(shù)據(jù),
//專門用于修改狀態(tài)的純函數(shù)
var reducer = (state=initState,action)=>{  
    return state;
}

export default reducer;

取出數(shù)據(jù)

store.getState()
import React, { Component } from 'react'
import store from '../store';
export default class One extends Component {
  constructor(props){
      super(props);
      this.state ={
          n:store.getState().n
      }
  }
  render() {
      let {n} = this.state;
    return (
      <div>
            {n}
      </div>
    )
  }
}

通過(guò)context獲取store數(shù)據(jù)

可以從頂層組件, 進(jìn)行跨組件傳值

就不用在每個(gè)頁(yè)面引入store,

引入store文件夾后

//index.js
import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './App';
import * as serviceWorker from './serviceWorker';
import {MyProvider} from "./components/MyProvider"
//引入store,并以參數(shù)的形式傳給App
import store from './store'
ReactDOM.render(
     <MyProvider store={store}>
        <App />
    </MyProvider>, document.getElementById('root'));
serviceWorker.unregister();
//MyProvider.js
import React, { Component,createContext } from 'react'
//createContext有兩個(gè)參數(shù),第一個(gè)是Provider,提供數(shù)據(jù),
//第二個(gè)參數(shù)是Consumer, 接受數(shù)據(jù)。
const ctx = React.createContext()

 class MyProvider extends Component {
  render() {
    console.log(this.props.a)
    return (
      <div>
        //接受App傳的參數(shù),即store
          <ctx.Provider value={this.props.store}>
            //渲染App
              {this.props.children}
          </ctx.Provider>
      </div>
    )
  }
}

export {MyProvider,ctx};//導(dǎo)出模塊和context, 在App.js里進(jìn)行包裹和傳參

方法1:

//One.js
import React, { Component } from 'react'

import Two from './Two'
import {ctx} from './MyProvider';
export default class One extends Component {
    //固定寫(xiě)法
    static contextType= ctx 
    constructor(props,context){
        super(props,context);
        this.state={
            //context相當(dāng)于store
            n:context.getState().n //就通過(guò)context來(lái)獲取,而不是store
        }
    }
  render() {
    return (
      <div>
          one
            {this.state.n}
         <Two />
      </div>
    )
  }
}

方法2:

//two.js
import React, { Component } from 'react'
import {ctx} from "./MyProvider"

export default class Two extends Component {
    static contextType = ctx;
  render() {
     
    return (
      <div>
        {this.context.getState().n}
         <ctx.Consumer>
             {
                 (value)=>{
                    return <div>{value}</div>
                 }
             }
         </ctx.Consumer>
      </div>
    )
  }
}

##修改數(shù)據(jù)

在組件里定義方法,發(fā)出動(dòng)作

store.dispatch()

inc(){
    //actionCreator.incActio 引入的actioncreator文件里面的incActio方法并傳參
        store.dispatch(actionCreator.incAction(5))// 發(fā)出動(dòng)作給reducer ,動(dòng)作對(duì)象必須有type屬性
    }
<button onClick={this.inc}>+</button>

在reducer里面判斷type,先淺拷貝一份副本,改變副本狀態(tài),返回新?tīng)顟B(tài)

var newState ={...state}; //副本
if(action.type==="INCREMENT"){
    // return {...state,n:state.n+action.p} 簡(jiǎn)寫(xiě)
    newState.n+=action.p; //修改副本的數(shù)據(jù)
    return newState; //返回新?tīng)顟B(tài)
}

數(shù)據(jù)改變后,視圖不會(huì)刷新 store.subscribe(callback) //監(jiān)控store 里數(shù)據(jù)的變化,數(shù)據(jù)變了,回調(diào)函數(shù)會(huì)執(zhí)行 重新從store取數(shù)據(jù),更新state,觸發(fā)渲染視圖

//在每個(gè)需要刷新的頁(yè)面地方寫(xiě)
constructor(props){
    super(props);
    this.state = {
        n:store.getState().n //獲取n
    }
    store.subscribe(this.change.bind(this)) //監(jiān)控store里的數(shù)據(jù)變化,一旦變化了,就執(zhí)行回調(diào)
}
change(){
    this.setState({
        n:store.getState().n //數(shù)據(jù)變化后立即把store的n重新賦值給n
    })
}

例:

//reducer.js
//保存數(shù)據(jù)在reducer里
var initState  ={
    n:1,
    list:[
        {
            "id":1,
            "title":'aa'
        },
        {
            "id":2,
            "title":'bb'
        },
        
    ],
    genId:2
}

export default (state=initState,action)=>{
    var newState ={...state}; //副本
    if(action.type==="INCREMENT"){
        // return {...state,n:state.n+action.p}
        newState.n+=action.p; //修改副本的數(shù)據(jù)
        return newState; //返回新?tīng)顟B(tài)
    }
    //加
    else if(action.type==="ADD"){
        newState.list.push({id:++newState.genId,title:action.title})
        return newState;
    }
    return state;
}
//two.js
import React, { Component } from 'react'
import store from '../../store'
import actionCreator from './actionCreator' //引入方法
export default class Two extends Component {
    constructor(props){
        super(props);
        this.state = {
            n:store.getState().n
        }
        store.subscribe(this.change.bind(this)) //監(jiān)控store里的數(shù)據(jù)變化,一旦變化了,就執(zhí)行回調(diào)
    }
    //數(shù)據(jù)變化后立即把store的n重新賦值給n
    change(){
        this.setState({
            n:store.getState().n
        })
    }
    inc(){
        store.dispatch(actionCreator.incAction(5))// 發(fā)出動(dòng)作給reducer ,動(dòng)作對(duì)象必須有type屬性
    }
  render() {
    return (
      <div>
        {this.state.n}
        <button onClick={this.inc}>+</button>//點(diǎn)擊加號(hào)增長(zhǎng)n
      </div>
    )
  }
}
//actionCreator.js
export default {
    //接受參數(shù)5,并傳給reducer.js
    incAction(p){
        return {
            type:'INCREMENT',
            p
        }
    }
}
//one.js
import React, { Component } from 'react'
import Two from '../two'
import store from '../../store';
import actionCreator from './actionCreator'
export default class One extends Component {
  
    constructor(props,context){
        super(props,context);
        this.state={
            n:store.getState().n,
            list:store.getState().list
        }
        store.subscribe(this.change.bind(this)) //監(jiān)控store里的數(shù)據(jù)變化,一旦變化了,就執(zhí)行回調(diào)
    }
    change(){
        this.setState({
            n:store.getState().n,
            list:store.getState().list
        })
    }
    add(e){
        if(e.keyCode===13){
             store.dispatch(actionCreator.addAction(e.target.value)); 
             e.target.value=""  //清空文本框
        }
    }
  render() {
    return (
      <div>
            {this.state.n}
            <input type="text" onKeyUp={this.add} />
            <ul>
                {
                    this.state.list.map((item)=>{
                        return <li key={item.id}>{item.title}</li>
                    })
                }
            </ul>
         <Two />
      </div>
    )
  }
}
//actionCreator.js
export default {
    addAction(title){
        return {
            type:'ADD',  //type 值不能重復(fù)  必須的
            title
        }
    }
}

模塊化

//store取數(shù)據(jù)
store.getState().變量
//context取數(shù)據(jù)
context.getState().模塊名.變量

//定義方法,發(fā)出動(dòng)作,用了context則更改為this.context
//調(diào)用引入的actionCreator文件里的removeAction方法
store.dispatch(actionCreator.removeAction())

//監(jiān)聽(tīng)數(shù)據(jù)變化,用context后把store更改為store即可
store.subscribe(this.定義的方法名.bind(this)) //監(jiān)聽(tīng)數(shù)據(jù)變化
**在components創(chuàng)建一個(gè)MyProvider
//此文件作用是,可以從頂層向底層傳參
//前兩行固定語(yǔ)法
import React, { Component, createContext } from 'react'
const context = createContext()
class MyProvider extends Component {
    render() {
        return (
            <div>
              //在index.js里包裹了App組件,所以接收用this.props
                <context.Provider value={this.props.store} >
                    {this.props.children}    
                </context.Provider>              
            </div>
        )
    }
}
//在index.js用MyProvider去包裹App組件,再在每個(gè)頁(yè)面的index.js里引入context
export { MyProvider, context }
**index.js
import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './App';
import * as serviceWorker from './serviceWorker';
//引入所有store數(shù)據(jù)
import store from './store'  
import {MyProvider} from './components/MyProvider'

ReactDOM.render(
                <MyProvider store={store}>
                    <App />
                </MyProvider>, document.getElementById('root'));
serviceWorker.unregister();
**store/index.js
//固定語(yǔ)法
import {createStore} from "redux";
import reducer from './reducer';  //專門用于修改狀態(tài)的純函數(shù)
var store = createStore(reducer); // 傳入ruducer 創(chuàng)建一個(gè)store

export default store;
**store/actionType.js
//保存名稱統(tǒng)一管理,只要用到ADD名稱的 都可以引入此文件
export const ADD = 'ADD'
export const REMOVE = 'REMOVE'
**store/reducer.js
//redux自帶combineReducers,可以把所有的reducer合并為一個(gè)
import {combineReducers} from 'redux'
import todoReducer from '../components/one/reducer' //引入one的reducer,里面寫(xiě)的方法和數(shù)據(jù)
var reducer = combineReducers({
    //模塊名:引入文件的名稱
    one:todoReducer 
})
export default reducer

做的添加和刪除功能

**one/index.js
import React, { Component } from 'react'
import { context } from '../MyProvider'//引入context獲取store數(shù)據(jù)
import actionCreator from './actionCreator' 

class OneRender extends Component {     //UI組件
    render() {
        return <div>
                    <input type="text" onKeyUp={this.props.add} />
                    <ul>
                        {
                            // 因?yàn)槭墙邮芨附M件的參數(shù)方法等,所以用this.props接受
                            this.props.list.map(item => {
                                return <li key={item.id}>
                                    {item.title}
                                    <button onClick={this.props.remove.bind(this, item.id)}>刪除</button>
                                </li>
                            })
                        }
                    </ul>
                </div>
    }
}
export default class One extends Component {    //容器組件
    // 固定寫(xiě)法
    static contextType = context;
    // 除了constructor以外,其他地方調(diào)用context都要加this
    constructor(props, context) {
        super(props, context);
        this.state = {
            // 用context之前:store.getState().變量
            //用了context后用:
            //context.getState().模塊名(此模塊名是在store/reducer.js定義的).變量
            list: context.getState().one.list,
        }
        this.add = this.add.bind(this)
        this.remove = this.remove.bind(this)
        context.subscribe(this.change.bind(this)) //監(jiān)聽(tīng)數(shù)據(jù)變化
    }
    change() {
        this.setState({
            list: this.context.getState().one.list
        })
    }
    add(e) {
        if (e.keyCode === 13) {
            this.context.dispatch(actionCreator.addAction(e.target.value));
            e.target.value = ""  //清空文本框
        }
    }
    remove(id) {
        this.context.dispatch(actionCreator.removeAction(id))
    }
    render() {
        return (
            <div>
                {/* 給子組件傳方法 */}
                <OneRender add={this.add} remove={this.remove} list={this.state.list} />
            </div>
        )
    }
}
**one/actionCreator.js
// 引入保存的名稱,那邊改了這邊跟著自動(dòng)更改
import { ADD, REMOVE } from '../../store/actionType'
export default {
    addAction(title){
        return {
            type:ADD,  //type 值不能重復(fù)  必須的
            title
        }
    },
    removeAction(id){
        return{
            type:REMOVE,
            id
        }
    }
}
**one/reducer.js
// 此文件是,在每個(gè)文件夾里寫(xiě)單獨(dú)的數(shù)據(jù)和方法,在store里的reduce引入此文件
// 變成:one:todoReducer ,所以可以通過(guò) context.getState().one.list獲取到數(shù)據(jù)
//引入store的固定變量,在store里改了則這邊就改變了
import {ADD,REMOVE} from '../../store/actionType'
var initState  ={
    list:[
        {
            "id":1,
            "title":'aa'
        },
        {
            "id":2,
            "title":'bb'
        },
    ],
    genId:2
}

export default (state=initState,action)=>{
    var newState ={...state}; //副本
    if(action.type===ADD){
        newState.list.push({id:++newState.genId,title:action.title})
        return newState;
    }
    else if(action.type===REMOVE){
        newState.list=newState.list.filter(item=>{
            return item.id !== action.id
        })
        return newState
    }
    return state;
}

react-redux

npm i react-redux

優(yōu)化上面的代碼,不需要容器組件,只需要UI組件

在index.js引入
import {Provider} from 'react-redux' 
包裹App組件
在需要的子組件index.js引入
import {connect} from 'react-redux'
在尾部導(dǎo)出組件
//第一個(gè)參數(shù)是數(shù)據(jù),括號(hào)里是倉(cāng)庫(kù)的數(shù)據(jù)(即reducer.js的數(shù)據(jù)),映射為state,第二個(gè)參數(shù)是所有的方法,后面的括號(hào)里寫(xiě)此類組件的名字
export default connect(mapState,mapDispatch)(OneRender)
**index.js
import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './App';
import * as serviceWorker from './serviceWorker';
import store from './store'
//引入下載的插件,就能拿到所有的store數(shù)據(jù)
import {Provider} from 'react-redux' 

ReactDOM.render(
                <Provider store={store}>
                    <App />
                </Provider>, document.getElementById('root'));
serviceWorker.unregister();

**one/index.js
import React, { Component } from 'react'
import actionCreator from './actionCreator' 
import {connect} from 'react-redux'
class OneRender extends Component {     //UI組件
    add=(e)=>{
        if(e.keyCode===13){
            this.props.addAction(e.target.value)
            e.target.value=''
        }
    }
    render() {
        console.log(this.props) //可以查看到所有方法和數(shù)據(jù)  
        return <div>
                    {/* 因?yàn)樾枰袛啵孕枰葘?xiě)個(gè)方法去判斷,判斷成功后再通過(guò)this.props.addAction去調(diào)用傳值 */}
                    <input type="text" onKeyUp={this.add} /> 
                    <ul>
                        {
                            // 因?yàn)槭墙邮芨附M件的參數(shù)方法等,所以用this.props接收,加上模塊名稱(此模塊名是在store/reducer.js定義的).變量
                            this.props.one.list.map(item => {
                                return <li key={item.id}>
                                    {item.title}
                                    {/* 方法名是在reducer里寫(xiě)的名字 */}
                                    <button onClick={this.props.removeAction.bind(this, item.id)}>刪除</button>
                                </li>
                            })
                        }
                    </ul>
                </div>
    }
}
//第一個(gè)參數(shù)是數(shù)據(jù),括號(hào)里是倉(cāng)庫(kù)的數(shù)據(jù),映射為state,第二個(gè)參數(shù)是所有的方法,后面的括號(hào)里寫(xiě)此類組件的名字
export default connect((state)=>state,actionCreator)(OneRender)
**one/index.js
//映射
import React, { Component } from 'react'
import actionCreator from './actionCreator' 
import {connect} from 'react-redux'
import { taggedTemplateExpression } from '@babel/types'
class OneRender extends Component {     //UI組件
    add=(e)=>{
        if(e.keyCode===13){
            this.props.add(e.target.value)
            e.target.value=''
        }
    }
    render() {
        return <div>
                    {/* 因?yàn)樾枰袛?,所以需要先?xiě)個(gè)方法去判斷,判斷成功后再通過(guò)this.props.addAction去調(diào)用傳值 */}
                    <input type="text" onKeyUp={this.add} /> 
                    <ul>
                        {
                            // 因?yàn)槭墙邮芨附M件的參數(shù)方法等,所以用this.props接收,加上模塊名稱(此模塊名是在store/reducer.js定義的).變量
                            this.props.list.map(item => {
                                return <li key={item.id}>
                                    {item.title}
                                    {/* 方法名是在reducer里寫(xiě)的名字 */}
                                    <button onClick={this.props.remove.bind(this, item.id)}>刪除</button>
                                </li>
                            })
                        }
                    </ul>
                </div>
    }
}
var mapState=(state)=>{
    return {
        // 實(shí)際上是變化了,并沒(méi)有監(jiān)控到數(shù)據(jù)變化
        list:state.one.list,
        //所以去主動(dòng)監(jiān)控一個(gè)數(shù)據(jù)隨時(shí)變化的值,不需要去渲染
        length:state.one.list.length
    }
}
var mapDispatch =(dispatch)=>{
    return {
        remove(id){
            dispatch(actionCreator.removeAction(id))
        },
        add(text){
            dispatch(actionCreator.addAction(text))
        }
    }
}
//第一個(gè)參數(shù)是數(shù)據(jù),括號(hào)里是倉(cāng)庫(kù)的數(shù)據(jù)(即reducer.js的數(shù)據(jù)),映射為state,第二個(gè)參數(shù)是所有的方法,后面的括號(hào)里寫(xiě)此類組件的名字
export default connect(mapState,mapDispatch)(OneRender)

異步請(qǐng)求

npm i redux-thunk

等異步請(qǐng)求后,再發(fā)出動(dòng)作

**store/index.js
//引入
import thunk from 'redux-thunk'
import {createStore,applyMiddleware} from 'redux';
import reducer from './reducer';  //總的reducer 

var store =  createStore(reducer,applyMiddleware(thunk));

export default store;
**list/index.js
import React, { Component } from 'react'
import {connect} from 'react-redux'
import actionCreator from './actionCreator'
 class List extends Component {
  componentDidMount(){
      //組件掛載后發(fā)送請(qǐng)求
      this.props.getData();
  }
  render() {
     console.log(this.props)
    return (
      <div>
            <ul>
                {//判斷,如果有再去遍歷
                   this.props.list.list.data &&  this.props.list.list.data.map((item)=>{
                        return <li key={item.id}>{item.title}</li>
                    })
                }
            </ul>
      </div>
    )
  }
}

export default connect((state)=>state,actionCreator)(List)
**list/actionCreator.js
//引入axios和寫(xiě)方法
import axios from 'axios';

//導(dǎo)出各種方法,并返回一個(gè)回調(diào)動(dòng)作給到reducer.js
export default {
    getData(){
        return (dispatch)=>{
              axios.get("http://jsonplaceholder.typicode.com/posts").then((res)=>{
                  dispatch({
                      type:'GETDATA',
                      list:res //把請(qǐng)求到的數(shù)據(jù)傳給
                  })
              })
        }
    }
}
**list/reducer.js
//寫(xiě)數(shù)據(jù)
var initState ={
    list:[]
}

 const reducer= (state = initState, action) => {
    switch (action.type) {
        case 'GETDATA':
            var newState ={...state};
            newState.list =action.list;
            return newState
        default:
            return state
    }
}

export default reducer;
**store/reducer.js
//redux自帶combineReducers,可以把所有的reducer合并為一個(gè)
import {combineReducers} from 'redux'
//引入list 的reducer,里面寫(xiě)的方法和數(shù)據(jù),這樣才可以在list.index.js里通過(guò)this.props.list得到數(shù)據(jù)
import listReducer from '../components/list/reducer'
var reducer = combineReducers({
    //模塊名:引入文件的名稱
    one:todoReducer ,
    list:listReducer
})
export default reducer

懶加載組件

yarn add ract-loadble

import React from 'react'
import Loadable from 'react-loadable';
//進(jìn)入home路徑才加載home
const Home = Loadable({
    loader: () => import('../App'),
    loading: ()=><div>Loading...</div>,
  });
  const NotFound = Loadable({
    loader: () => import('../components/notfound'),
    loading: ()=><div>Loading...</div>,
  });

//當(dāng)路徑為home時(shí)加載home組件
export const routes =[
    {
        path:'/home',
        component:Home
    },
    {
        path:'/404',
        component:NotFound
    }
]

重塑組件

作用是為了樣式可以繼承

安裝:npm install styled-components

//使用:(首字母一定要大寫(xiě))
import styled from 'styled-components'
//創(chuàng)建一個(gè)div ,組件名稱為Div 
var Div = styled.div`
    background:red;
    font-weight:bold;
    p{
        font-style:italic;
    }
`
function App() {
  return (
    <>
        <Div>
            <p>111</p>
            <p>222</p>
        </Div>
    </>
  );
}

空標(biāo)記Fragment

//方案1
import React,{Fragment} from 'react';
function App() {
  return (
    <Fragment>
        <One />
    </Fragment>
  );
}
//方案2
import React from 'react';
function App() {
  return (
    <>
        <One />
    </>
  );
}

react 其他stuff

類似vue的slot插槽

react沒(méi)有這個(gè)東西,而是children

但是跟插槽一樣,在使用組件的地方內(nèi)部直接放入要放的標(biāo)簽,然后用this.props.children[0]使用

{
  //children是個(gè)數(shù)組
  this.props.children[0]
}

類似vue的v-html

<div dangerouslySetInnerHTML={{
                    __html:this.state.content
  }}></div>

Hooks

React目前提供的Hook
hook    用途
useState    設(shè)置和改變state,代替原來(lái)的state和setState
useEffect   代替原來(lái)的生命周期,componentDidMount,componentDidUpdate 和 componentWillUnmount 的合并版
useLayoutEffect 與 useEffect 作用相同,但它會(huì)同步調(diào)用 effect
useMemo 控制組件更新條件,可根據(jù)狀態(tài)變化控制方法執(zhí)行,優(yōu)化傳值
useCallback useMemo優(yōu)化傳值,usecallback優(yōu)化傳的方法,是否更新
useRef  跟以前的ref,一樣,只是更簡(jiǎn)潔了
useContext  上下文爺孫及更深組件傳值
useReducer  代替原來(lái)redux里的reducer,配合useContext一起使用
useDebugValue   在 React 開(kāi)發(fā)者工具中顯示自定義 hook 的標(biāo)簽,調(diào)試使用。
useImperativeHandle 可以讓你在使用 ref 時(shí)自定義暴露給父組件的實(shí)例值。
  • userState():狀態(tài)鉤子

純函數(shù)組件沒(méi)有狀態(tài),useState()用于為函數(shù)組件引入狀態(tài)

/*
  count - 定義的值
  setCount - 更改值的方法
  0 - 設(shè)置的初始值
*/
const [ count, setCount ] = useState(0)
  • useContext():共享狀態(tài)鉤子

該鉤子的作用是,在組件之間共享狀態(tài),作用就是做到狀態(tài)分發(fā)

但是必須要有Provider進(jìn)行組件包裹

簡(jiǎn)單來(lái)說(shuō)useContext只是拿值的,值是在provider上面定義的

import React,{ useContext } from 'react'
const Ceshi = () => {
  const AppContext = React.createContext({})
  const A =() => {
    const { name } = useContext(AppContext)
    return (
        <p>我是A組件的名字{name}<span>我是A的子組件{name}</span></p>
    )
}
const B =() => {
  const { name } = useContext(AppContext)
  return (
      <p>我是B組件的名字{name}</p>
  )
}
  return (
    <AppContext.Provider value={{name: 'hook測(cè)試'}}>
    <A/>
    <B/>
    </AppContext.Provider>
  )
}
export default Ceshi 

  • useReducer():Action鉤子

    useReducer 通常被用于替代useState來(lái)統(tǒng)一管理狀態(tài),避免過(guò)多的state帶來(lái)的開(kāi)發(fā)不便

我們知道,在使用React的過(guò)程中,如遇到狀態(tài)管理,我們一般會(huì)用到Redux,而React本身是不提供狀態(tài)管理的。而useReducer()為我們提供了狀態(tài)管理。首先,關(guān)于redux我們都知道,其原理是我們通過(guò)用戶在頁(yè)面中發(fā)起action,從而通過(guò)reducer方法來(lái)改變state,從而實(shí)現(xiàn)頁(yè)面和狀態(tài)的通信。而Reducer的形式是(state, action) => newstate。類似,我們的useReducer()是這樣的

const [state, dispatch] = useReducer(reducer, initialState)

它接受reducer函數(shù)和狀態(tài)的初始值作為參數(shù),返回一個(gè)數(shù)組,其中第一項(xiàng)為當(dāng)前的狀態(tài)值,第二項(xiàng)為發(fā)送action的dispatch函數(shù)。下面我們依然用來(lái)實(shí)現(xiàn)一個(gè)計(jì)數(shù)器。
和redux一樣,我們是需要通過(guò)頁(yè)面組件發(fā)起action來(lái)調(diào)用reducer方法,從而改變狀態(tài),達(dá)到改變頁(yè)面UI的這樣一個(gè)過(guò)程。所以我們會(huì)先寫(xiě)一個(gè)Reducer函數(shù),然后通過(guò)useReducer()返回給我們的state和dispatch來(lái)驅(qū)動(dòng)這個(gè)數(shù)據(jù)流。思路就是這樣,下面我們上代碼

import React,{useReducer} from 'react'

const AddCount = () => {
const reducer = (state, action) =>  {
 if(action.type === ''add){
  return {
  ...state,
  count: state.count +1,
  }
 }else {
   return state
  }
 }
const addcount = () => { 
  dispatch({
    type: 'add'
  })
 }
const [state, dispatch] = useReducer(reducer, {count: 0})
return (
<>
<p>{state.count}</p>
<button onClick={addcount}>count++</button>
</>
)
}
export default AddCount

  • useEffect():副作用鉤子

useEffect()接受兩個(gè)參數(shù),第一個(gè)參數(shù)是你要進(jìn)行的異步操作,第二個(gè)參數(shù)是一個(gè)數(shù)組,用來(lái)給出Effect的依賴項(xiàng)。只要這個(gè)數(shù)組發(fā)生變化,useEffect()就會(huì)執(zhí)行。當(dāng)?shù)诙?xiàng)省略不填時(shí),useEffect()會(huì)在每次組件渲染時(shí)執(zhí)行。這一點(diǎn)類似于類組件的componentDidMount。下面我們通過(guò)代碼模擬一個(gè)異步加載數(shù)據(jù)。

const [myValue,settMyValue] = useState('') 
//useEffect(處理函數(shù),【依賴】)   副作用
useEffect(()=>{
   //創(chuàng)建或者更新的時(shí)候執(zhí)行
   console.log('useEffect')
 },[myValue])//依賴myValue 每當(dāng)這個(gè)參數(shù)有變化的時(shí)候就會(huì)觸發(fā)當(dāng)前這個(gè)useEffect
  • useMemo

把“創(chuàng)建”函數(shù)和依賴項(xiàng)數(shù)組作為參數(shù)傳入 useMemo,它僅會(huì)在某個(gè)依賴項(xiàng)改變時(shí)才重新計(jì)算 memoized 值。這種優(yōu)化有助于避免在每次渲染時(shí)都進(jìn)行高開(kāi)銷的計(jì)算。

function Example() {
    const [count, setCount] = useState(1);
    const [val, setValue] = useState('');
 
    const getNum = useMemo(() => {
        return Array.from({length: count * 100}, (v, i) => i).reduce((a, b) => a+b)
    }, [count])
 
    return <div>
        <h4>總和:{getNum}</h4>
        <div>
            <button onClick={() => setCount(count + 1)}>+1</button>
            <input value={val} onChange={event => setValue(event.target.value)}/>
        </div>
    </div>;
}
  • useCallback

把內(nèi)聯(lián)回調(diào)函數(shù)及依賴項(xiàng)數(shù)組作為參數(shù)傳入 useCallback,它將返回該回調(diào)函數(shù)的 memoized 版本,該回調(diào)函數(shù)僅在某個(gè)依賴項(xiàng)改變時(shí)才會(huì)更新。當(dāng)你把回調(diào)函數(shù)傳遞給經(jīng)過(guò)優(yōu)化的并使用引用相等性去避免非必要渲染(例如 shouldComponentUpdate)的子組件時(shí),它將非常有用。

  • useCallback和 useMemo區(qū)別

useMemouseCallback接收的參數(shù)都是一樣,都是在其依賴項(xiàng)發(fā)生變化后才執(zhí)行,都是返回緩存的值,區(qū)別在于useMemo返回的是函數(shù)運(yùn)行的結(jié)果,useCallback返回的是函數(shù)。

  • useMemo 緩存的結(jié)果是回調(diào)函數(shù)中return回來(lái)的值,主要用于緩存計(jì)算結(jié)果的值,應(yīng)用場(chǎng)景如需要計(jì)算的狀態(tài)
  • useCallback 緩存的結(jié)果是函數(shù),主要用于緩存函數(shù),應(yīng)用場(chǎng)景如需要緩存的函數(shù),因?yàn)楹瘮?shù)式組件每次任何一個(gè)state發(fā)生變化,會(huì)觸發(fā)整個(gè)組件更新,一些函數(shù)是沒(méi)有必要更新的,此時(shí)就應(yīng)該緩存起來(lái),提高性能,減少對(duì)資源的浪費(fèi);另外還需要注意的是,useCallback應(yīng)該和React.memo配套使用,缺了一個(gè)都可能導(dǎo)致性能不升反而下降。
useCallback(fn, deps) 相當(dāng)于 useMemo(() => fn, deps)

react事件綁定this的原因

如何綁定this

//第一種,在constructor里面用bind綁定this
constructor(props) {
    super(props);
    this.state = {isToggleOn: true};

    // This binding is necessary to make `this` work in the callback
  //這種方法是把原型方法handleClick()改變?yōu)閷?shí)例方法handleClick(),并且強(qiáng)制制定這個(gè)方法中的this指向當(dāng)前的實(shí)例
    this.handleClick = this.handleClick.bind(this);
  }
//第二種,聲明方法的時(shí)候使用箭頭函數(shù)
  handleClick = () => {
    this.setState(prevState => ({
      isToggleOn: !prevState.isToggleOn
    }));
  }
//第三種,調(diào)用的時(shí)候使用箭頭函數(shù)
   render() {
    return (
      <button onClick={ () => { this.handleClick } }> 
        {this.state.isToggleOn ? 'ON' : 'OFF'} 
      </button>
    );
  }

在 render() 函數(shù)中,react對(duì){}的解析會(huì)把this的指向解除了,所以在這里導(dǎo)致的 this 丟失,指向了 undefined。 生成實(shí)例的過(guò)程中,構(gòu)造器函數(shù) constructor() 必執(zhí)行。當(dāng)執(zhí)行到下面語(yǔ)句時(shí),分析代碼: this.handleClick = this.handleClick.bind(this); 賦值語(yǔ)句右側(cè),this.handleClick 執(zhí)行,首先在當(dāng)前實(shí)例上查找 handleClick 方法,當(dāng)前實(shí)例沒(méi)有,然后沿原型鏈向上查找到原型方法 handleClick ,再執(zhí)行 bind(this) ,將原型方法中的 this 指向新生成的實(shí)例

react事件處理機(jī)制

  • JS原生處理機(jī)制

    在JavaScript中,事件的觸發(fā)實(shí)質(zhì)上是要經(jīng)過(guò)三個(gè)階段:事件捕獲、目標(biāo)對(duì)象本身的事件處理和事件冒泡,假設(shè)在div中觸發(fā)了click事件,實(shí)際上首先經(jīng)歷捕獲階段會(huì)由父級(jí)元素將事件一直傳遞到事件發(fā)生的元素,執(zhí)行完目標(biāo)事件本身的處理事件后,然后經(jīng)歷冒泡階段,將事件從子元素向父元素冒泡。

React事件并沒(méi)有原生的綁定在真實(shí)的DOM上,而是使用了行為委托方式實(shí)現(xiàn)事件機(jī)制,所以react的事件是合成事件((Synethic event),不是原生事件

合成事件與原生事件的區(qū)別

  • 寫(xiě)法不同,合成事件是駝峰寫(xiě)法,而原生事件是全部小寫(xiě)
  • 執(zhí)行時(shí)機(jī)不同,合適事件全部委托到document上,而原生事件綁定到DOM元素本身
  • 合成事件中可以是任何類型,比如this.handleClick這個(gè)函數(shù),而原生事件中只能是字符串

合成事件:React不會(huì)將事件處理函數(shù)直接綁定到真實(shí)的節(jié)點(diǎn)上,而是把所有的事件綁定到結(jié)構(gòu)的最外層,使用一個(gè)統(tǒng)一的事件監(jiān)聽(tīng)器。這個(gè)監(jiān)聽(tīng)器維持了一個(gè)映射,保存所有組件內(nèi)部的事件監(jiān)聽(tīng)和處理函數(shù)。當(dāng)事件發(fā)生時(shí),首先被這個(gè)統(tǒng)一的事件監(jiān)聽(tīng)器處理,然后在映射里找到真正的事件處理函數(shù)并調(diào)用。

  • React中的事件機(jī)制分為兩個(gè)階段:事件注冊(cè)和事件觸發(fā):
  1. 事件注冊(cè)  
    React在組件加載(mount)和更新(update)時(shí),其中的ReactDOMComponent會(huì)對(duì)傳入的事件屬性進(jìn)行處理,對(duì)相關(guān)事件進(jìn)行注冊(cè)和存儲(chǔ)。document中注冊(cè)的事件不處理具體的事件,僅對(duì)事件進(jìn)行分發(fā)。ReactBrowserEventEmitter作為事件注冊(cè)入口,擔(dān)負(fù)著事件注冊(cè)和事件觸發(fā)。注冊(cè)事件的回調(diào)函數(shù)由EventPluginHub來(lái)統(tǒng)一管理,根據(jù)事件的類型(type)和組件標(biāo)識(shí)(_rootNodeID)為key唯一標(biāo)識(shí)事件并進(jìn)行存儲(chǔ)。
  2. 事件執(zhí)行
    事件執(zhí)行時(shí),document上綁定事件ReactEventListener.dispatchEvent會(huì)對(duì)事件進(jìn)行分發(fā),根據(jù)之前存儲(chǔ)的類型(type)和組件標(biāo)識(shí)(_rootNodeID)找到觸發(fā)事件的組件。ReactEventEmitter利用EventPluginHub中注入(inject)的plugins(例如:SimpleEventPluginEnterLeaveEventPlugin)會(huì)將原生的DOM事件轉(zhuǎn)化成合成的事件,然后批量執(zhí)行存儲(chǔ)的回調(diào)函,回調(diào)函數(shù)的執(zhí)行分為兩步,第一步是將所有的合成事件放到事件隊(duì)列里面,第二步是逐個(gè)執(zhí)行。需要注意的是,瀏覽器原生會(huì)為每個(gè)事件的每個(gè)listener創(chuàng)建一個(gè)事件對(duì)象,可以從這個(gè)事件對(duì)象獲取到事件的引用。這會(huì)造成高額的內(nèi)存分配,React在啟動(dòng)時(shí)就會(huì)為每種對(duì)象(比如點(diǎn)擊事件,滾動(dòng)事件)分配內(nèi)存池,用到某一個(gè)事件對(duì)象時(shí)就可以從這個(gè)內(nèi)存池進(jìn)行復(fù)用,節(jié)省內(nèi)存。

React事件處理的特性

React的事件系統(tǒng)和瀏覽器事件系統(tǒng)相比,主要增加了兩個(gè)特性:事件代理和事件自動(dòng)綁定

React 監(jiān)聽(tīng)的是 document 上的冒泡階段。事件冒泡到 document 后,React 將事件再派發(fā)到組件樹(shù)中,然后事件開(kāi)始在組件樹(shù) DOM 中走捕獲冒泡流程。

1、事件代理

  1. 區(qū)別于瀏覽器事件處理方式,React并未將事件處理函數(shù)與對(duì)應(yīng)的DOM節(jié)點(diǎn)直接關(guān)聯(lián),而是在頂層使用了一個(gè)全局事件監(jiān)聽(tīng)器監(jiān)聽(tīng)所有的事件;
  2. React會(huì)在內(nèi)部維護(hù)一個(gè)映射表記錄事件與組件事件處理函數(shù)的對(duì)應(yīng)關(guān)系;
  3. 當(dāng)某個(gè)事件觸發(fā)時(shí),React根據(jù)這個(gè)內(nèi)部映射表將事件分派給指定的事件處理函數(shù);
  4. 當(dāng)映射表中沒(méi)有事件處理函數(shù)時(shí),React不做任何操作;
  5. 當(dāng)一個(gè)組件安裝或者卸載時(shí),相應(yīng)的事件處理函數(shù)會(huì)自動(dòng)被添加到事件監(jiān)聽(tīng)器的內(nèi)部映射表中或從表中刪除。

2、事件自動(dòng)綁定

  1. 在JavaScript中創(chuàng)建回調(diào)函數(shù)時(shí),一般要將方法綁定到特定的實(shí)例,以保證this的正確性;

  2. 在React中,每個(gè)事件處理回調(diào)函數(shù)都會(huì)自動(dòng)綁定到組件實(shí)例(使用ES6語(yǔ)法創(chuàng)建的例外);

注意:事件的回調(diào)函數(shù)被綁定在React組件上,而不是原始的元素上,即事件回調(diào)函數(shù)中的this所指的是組件實(shí)例而不是DOM元素;

3、合成事件

1.與瀏覽器事件處理稍微有不同的是,React中的事件處理程序所接收的事件參數(shù)是被稱為“合成事件(SyntheticEvent)的實(shí)例。合成事件是對(duì)瀏覽器原生事件跨瀏覽器的封裝,并與瀏覽器原生事件有著同樣的接口,如stopPropagation(),preventDefault()等,并且這些接口是跨瀏覽器兼容的。
2.如果需要使用瀏覽器原生事件,可以通過(guò)合成事件的nativeEvent屬性獲取

React在事件處理的優(yōu)點(diǎn)

  1. 幾乎所有的事件代理(delegate)到 document ,達(dá)到性能優(yōu)化的目的
  2. 對(duì)于每種類型的事件,擁有統(tǒng)一的分發(fā)函數(shù) dispatchEvent
  3. 事件對(duì)象(event)是合成對(duì)象(SyntheticEvent),不是原生的,其具有跨瀏覽器兼容的特性
  4. react內(nèi)部事件系統(tǒng)實(shí)現(xiàn)可以分為兩個(gè)階段: 事件注冊(cè)、事件分發(fā),幾乎所有的事件均委托到document上,而document上事件的回調(diào)函數(shù)只有一個(gè):ReactEventListener.dispatchEvent,然后進(jìn)行相關(guān)的分發(fā)
  5. 對(duì)于冒泡事件,是在 document 對(duì)象的冒泡階段觸發(fā)。對(duì)于非冒泡事件,例如focus,blur,是在 document 對(duì)象的捕獲階段觸發(fā),最后在 dispatchEvent 中決定真正回調(diào)函數(shù)的執(zhí)行

diff算法

fibber是一個(gè)雙緩存結(jié)構(gòu)

dom-diff就是對(duì)比老的fiber鏈表和新的jsx數(shù)組對(duì)比,生成新的fiber鏈表過(guò)程

拿老的fiber節(jié)點(diǎn)與心的jsx節(jié)點(diǎn)進(jìn)行對(duì)比,每個(gè)fiber節(jié)點(diǎn)都有type 和 key 只要不一樣就刪舊增新

如果是單個(gè)節(jié)點(diǎn)就直接根據(jù)type key進(jìn)行替換刪除

多個(gè)節(jié)點(diǎn)就有兩輪,第一輪進(jìn)行節(jié)點(diǎn)的更新 第二輪才是替換刪除

在第一輪里,如果有type或key不一樣,則馬上終止第一輪循環(huán),并且創(chuàng)建一個(gè)map對(duì)象,把所有對(duì)象以key,dom鍵值對(duì)形式存入遍歷,然后找是否有相同的節(jié)點(diǎn),有的話就移動(dòng)這個(gè)找到的相同節(jié)點(diǎn)

連續(xù)setState

連續(xù)的情況只會(huì)執(zhí)行一次,會(huì)把連續(xù)做的state做個(gè)合并,如果key是一樣的就會(huì)后面覆蓋前面的,不是一樣的就做合并,原理是基于object.assign合并對(duì)象

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容