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ū)別在于:
- 在 React 開(kāi)啟異步渲染模式后,在 render 階段讀取到的 DOM 元素狀態(tài)并不總是和 commit 階段相同,這就導(dǎo)致在
componentDidUpdate 中使用 componentWillUpdate 中讀取到的 DOM 元素狀態(tài)是不安全的,因?yàn)檫@時(shí)的值很有可能已經(jīng)失效了。 - 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ū)別
useMemo和useCallback接收的參數(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ā):
- 事件注冊(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ǔ)。 - 事件執(zhí)行
事件執(zhí)行時(shí),document上綁定事件ReactEventListener.dispatchEvent會(huì)對(duì)事件進(jìn)行分發(fā),根據(jù)之前存儲(chǔ)的類型(type)和組件標(biāo)識(shí)(_rootNodeID)找到觸發(fā)事件的組件。ReactEventEmitter利用EventPluginHub中注入(inject)的plugins(例如:SimpleEventPlugin、EnterLeaveEventPlugin)會(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、事件代理
- 區(qū)別于瀏覽器事件處理方式,React并未將事件處理函數(shù)與對(duì)應(yīng)的DOM節(jié)點(diǎn)直接關(guān)聯(lián),而是在頂層使用了一個(gè)全局事件監(jiān)聽(tīng)器監(jiān)聽(tīng)所有的事件;
- React會(huì)在內(nèi)部維護(hù)一個(gè)映射表記錄事件與組件事件處理函數(shù)的對(duì)應(yīng)關(guān)系;
- 當(dāng)某個(gè)事件觸發(fā)時(shí),React根據(jù)這個(gè)內(nèi)部映射表將事件分派給指定的事件處理函數(shù);
- 當(dāng)映射表中沒(méi)有事件處理函數(shù)時(shí),React不做任何操作;
- 當(dāng)一個(gè)組件安裝或者卸載時(shí),相應(yīng)的事件處理函數(shù)會(huì)自動(dòng)被添加到事件監(jiān)聽(tīng)器的內(nèi)部映射表中或從表中刪除。
2、事件自動(dòng)綁定
在JavaScript中創(chuàng)建回調(diào)函數(shù)時(shí),一般要將方法綁定到特定的實(shí)例,以保證this的正確性;
在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)
- 幾乎所有的事件代理(delegate)到 document ,達(dá)到性能優(yōu)化的目的
- 對(duì)于每種類型的事件,擁有統(tǒng)一的分發(fā)函數(shù) dispatchEvent
- 事件對(duì)象(event)是合成對(duì)象(SyntheticEvent),不是原生的,其具有跨瀏覽器兼容的特性
- react內(nèi)部事件系統(tǒng)實(shí)現(xiàn)可以分為兩個(gè)階段: 事件注冊(cè)、事件分發(fā),幾乎所有的事件均委托到document上,而document上事件的回調(diào)函數(shù)只有一個(gè):ReactEventListener.dispatchEvent,然后進(jìn)行相關(guān)的分發(fā)
- 對(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ì)象