React

初始化React

從 html 開始你的第一個 react 頁面。引入三個文件:react核心文件船逮、react-dom文件和轉(zhuǎn)化jsx的babel顾腊。
然后聲明一個變量,變量的值是一個不帶引號的標(biāo)簽挖胃,最后使用 ReactDOM.render 方法把該變量渲染到頁面上杂靶。

<div id="app"></div>
<script type="text/babel">
    const VDOM = <h1>你好,里愛特</h1>
    ReactDOM.render(VDOM,document.querySelector('#app'))
</script>

創(chuàng)建虛擬DOM的兩種方式

第一種:JSX語法

現(xiàn)階段需借助babel

<div id="app"></div>
<script type="text/babel">
    const VDOM = (
        <h1>
            <p class="hello-react">你好酱鸭,里愛特</p>
        </h1>
    )
    ReactDOM.render(VDOM,document.querySelector('#app'))
</script>

第二種:借助 React.createElement

此方法僅供了解吗垮,后續(xù)也不會使用

<div id="app"></div>
<script>
    const VDOM = React.createElement('h1',{},React.createElement('span',{class:"hello-react"},'你好,里愛特'))
    ReactDOM.render(VDOM,document.querySelector('#app'))
</script>

參數(shù)一凛辣,元素類型抱既。參數(shù)二职烧,配置對象扁誓,用于寫元素屬性防泵。參數(shù)三,元素的內(nèi)容

關(guān)于 虛擬DOM

<div id="app"></div>
<script type="text/babel">
    const VDOM = <h1>你好蝗敢,里愛特</h1>
    console.log(VDOM)
    console.dir(document.querySeletor('#app'))
</script>

創(chuàng)建出來后打印臺輸出可以看到 VDOM 本質(zhì)上就是一個 object 類型的變量捷泞,身上帶有自身特有的虛擬DOM屬性。
與真實(shí) DOM 比較可以發(fā)現(xiàn) react 虛擬 DOM 很輕量

JSX 語法規(guī)則

  1. 虛擬DOM只能有一個根標(biāo)簽寿谴。
  2. 虛擬DOM中每一個標(biāo)簽都必須閉合锁右,單標(biāo)簽例如 input 也要寫成自閉合形式。
  3. JSX 標(biāo)簽變量不能帶引號讶泰。
  4. 虛擬DOM中如果想使用JS表達(dá)式咏瑟,需要包裹在花括號里面。
  5. 虛擬DOM中書寫樣式的兩個注意點(diǎn)
    1. 如果虛擬 DOM 中想要帶上 class 痪署,注意要寫成 className码泞,原因是跟ES6的class關(guān)鍵字沖突了。
    2. 如果虛擬 DOM 中想要寫內(nèi)聯(lián)樣式狼犯,style后面的值不能接字符串余寥,需要接一個 JS 表達(dá)式,表達(dá)式里面是對象形式悯森。
  6. 虛擬DOM中的標(biāo)簽如果是小寫字母開頭的標(biāo)簽宋舷,JSX 默認(rèn)解析為 html 原生標(biāo)簽。
    虛擬DOM中的標(biāo)簽如果是大寫字母開頭的標(biāo)簽瓢姻,JSX 默認(rèn)解析為 react 組件祝蝠。
  7. 虛擬DOM注釋 {/* */}
<div id="app"></div>
<script type="text/babel">
    const myData = '你好,里愛特幻碱!'
    const jsxml = (
        <div>
            <h1 className="red">{myData}</h1>
            <input type="text" style={
                {color:'blue'}
            }/>
            <Good>顧得</Good>
        </div>
    )
    ReactDOM.render(jsxml,document.querySelector('#app'))
</script>

使用 react 渲染可迭代數(shù)據(jù)為DOM

使用JS表達(dá)式的形式渲染可迭代數(shù)據(jù)续膳,例如用 map 渲染數(shù)組,map 的回調(diào)函數(shù) return 虛擬DOM收班,且必須攜帶 key 方便react執(zhí)行diff算法坟岔。

const data = ['angular','react','vue']
const VDOM = (
    <div>
        <h1>使用react渲染可迭代數(shù)據(jù)</h1>
        <ul>
        {
            data.map((item,index)=>{
                return <li key={index}>{item}</li>
            })  
        }
        </ul>
    </div>
)
ReactDOM.render(VDOM,document.querySelector('#app'))

組件

在使用 ReactDOM.render() 方法的時(shí)候,第一個參數(shù)傳入一個標(biāo)簽摔桦,就會尋找與標(biāo)簽同名的 function 或 class社付,進(jìn)而去創(chuàng)建組件實(shí)例。

函數(shù)式組件(簡單組件)

function MyComponent(){
    return <h2>我是react函數(shù)式組件邻耕,我的this指向?yàn)閲?yán)格模式下的js的undefined</h2>
}
ReactDOM.render(<MyComponent/>,document.querySelector('#app'))

類式組件(復(fù)雜組件)

類式組件式繼承 React.Component 父類的之類鸥咖,必須帶有 render() 方法,render中的 this 指向 組件實(shí)例對象兄世。
組件實(shí)例對象會在調(diào)用 ReactDOM.render 時(shí)候自動創(chuàng)建出來啼辣。

<div id="app"></div>
<script type="text/babel">
    class MyComponent extends React.Component {
        render() {
            console.log(this);
            return (
                <h1>我是類式組件創(chuàng)建出來的組件</h1>
            )
        }
    }
    ReactDOM.render(<MyComponent />, document.querySelector('#app'))
</script>

組件的三大屬性

state、props御滩、refs

state

往組件身上添加狀態(tài)鸥拧,需要在組件類里的 constructor 里寫上 this.state={}
注意在添加state之前必須調(diào)用 super()

class RC extends React.Component {
    render() {
        return (<h1>今天天氣很{this.state.hot}</h1>)
    }
    constructor() {
        super()
        this.state = {
            hot: '熱'
        }
    }
}
ReactDOM.render(<RC />, document.getElementById('app'))

解決虛擬DOM實(shí)例上的this指向問題

在 constructor 中利用 bind(this) 解決

class RC extends React.Component {
    render() {
        const { num } = this.state
        return (
            <h1 onClick={this.countPlus}>天下第{num}武道會</h1>
        )
    }
    constructor() {
        super();
        this.state = {
            num: 1
        };
        this.countPlus = this.countPlus.bind(this)
    }
    countPlus() {
        this.state.num++
        console.log(this.state.num);
    }
}
ReactDOM.render(<RC />, document.getElementById('app'))

setState

上面的例子可以看到頁面并沒有更新党远,那是因?yàn)橹苯有薷膕tate的話不是響應(yīng)式的,需要調(diào)用 setState 修改狀態(tài)富弦,才能刷新 render

class RC extends React.Component {
    render() {
        const { num } = this.state
        return (
            <h1 onClick={this.countPlus}>天下第{num}武道會</h1>
        )
    }
    constructor() {
        super();
        this.state = {
            num: 1
        };
        this.countPlus = this.countPlus.bind(this)
    }
    countPlus() {
        let newNum = this.state.num + 1;
        this.setState({
            num: newNum
        })
    }
}
ReactDOM.render(<RC />, document.getElementById('app'))

精簡組件寫法

我們可以看到上面的constructor寫法很麻煩沟娱,大多數(shù)時(shí)候并不需要用到類本身,而是用類的實(shí)例對象腕柜。
在class中直接寫賦值語句可以為實(shí)例對象直接增加屬性济似,用于事件觸發(fā)的回調(diào)函數(shù)也可以寫成箭頭函數(shù)賦值給變量的形式(因?yàn)橐尯瘮?shù)里的this指向組件實(shí)例)

class RC extends React.Component {
    render() {
        const { name, age } = this.state
        return (
            <div>
                <h1>我叫{name},我今年{age}歲</h1>
                <button onClick={this.handleClick}>點(diǎn)我增加歲數(shù)</button>
            </div>
        )
    }
    state = {
        name: 'ming',
        age: 18
    }
    handleClick = () => {
        let newAge = this.state.age + 1
        this.setState({
            age: newAge
        })
    }
}
ReactDOM.render(<RC />, document.querySelector('#app'))

props

通過渲染組件時(shí)往組件身上寫屬性和屬性值可以往組件內(nèi)部傳遞數(shù)據(jù)盏缤,通過 this.props.xxx 可以讀取傳遞過來的屬性值

<div id="app"></div>
<div id="app2"></div>
<script type="text/babel">
    class Person extends React.Component {
        render() {
            return (
                <div>
                    <ul>
                        <li>我是:{this.props.name}</li>
                        <li>年齡:{this.props.age}</li>
                        <li>性別:{this.props.sex}</li>
                    </ul>
                </div>
            )
        }
    }
    ReactDOM.render(<Person name="he" age="18" sex="male" />, document.getElementById('app'))
    ReactDOM.render(<Person name="ming" age="20" sex="female" />, document.getElementById('app2'))

props 批量傳遞

可以傳遞一個對象砰蠢,然后用花括號里面展開對象的形式進(jìn)行 props 傳值

class Person extends React.Component {
    render() {
        return (
            <div>
                <ul>
                    <li>我是:{this.props.name}</li>
                    <li>年齡:{this.props.age}</li>
                    <li>性別:{this.props.sex}</li>
                </ul>
            </div>
        )
    }
}
const qiming = {
    name: 'he',
    age: 18,
    sex: 'male'
}
ReactDOM.render(<Person {...qiming} />, document.getElementById('app'))

在這個例子中,babel+react里僅限組件傳值可以這樣展開對象唉铜,其他情況展開是個空白的東西

props 限制

對props傳遞的值進(jìn)行類型限制或必要性限制娩脾,需要借助另一個 react 庫 prop-type。
通過往組件身上添加 propTypes 屬性進(jìn)行類型限制和必要性限制打毛,添加 defaultProps 進(jìn)行默認(rèn)屬性值設(shè)置柿赊。

class Person extends React.Component {
    render() {
        const { name, age, sex } = this.props
        return (
            <div>
                我是{name}
            </div>
        )
    }
}
Person.propTypes = {
    name: PropTypes.string.isRequired,
    age: PropTypes.number,
    speak: PropTypes.func.isRequired
}
Person.defaultProps = {
    name: 'he'
}
const p1 = {
    name: 'qiming',
    age: 18,
    sex: 'male'
}
const speak = () => {
    console.log('haha');
}
ReactDOM.render(<Person {...p1} speak={speak} />, document.querySelector('#app'))

如果要在限制對象內(nèi)使用進(jìn)一步限制,使用 PropTypes.shape 方法

PropTypes.shape({
    color: PropTypes.string,
    fontSize: PropTypes.number
})

props 限制的簡寫形式

上面的代碼幻枉,可以使用 static 關(guān)鍵字優(yōu)化一下

class Person extends React.Component {
    render() {
        const { name, age, sex } = this.props
        return (
            <div>
                我是{name}
            </div>
        )
    }
    static propTypes = {
        name: PropTypes.string.isRequired,
        age: PropTypes.number,
        speak: PropTypes.func.isRequired
    }
    static defaultProps = {
        name: 'he'
    }
}

函數(shù)式組件中的 props

函數(shù)式組件中碰声,在形參中的第一個參數(shù),就是props熬甫,當(dāng)然也可以使用 props限制

function RC(props) {
    console.log(props);
    return <div></div>
}
RC.propTypes = {
    name: PropTypes.string.isRequired,
    age: PropTypes.number,
    speak: PropTypes.func.isRequired
}
ReactDOM.render(<RC name="ming" />, document.getElementById('app'))

props 注意點(diǎn)

  1. props 是只讀的胰挑,單向數(shù)據(jù)流保持?jǐn)?shù)據(jù)的可追溯性

construtor

官方文檔:通常,在 React 中椿肩,構(gòu)造函數(shù)僅用于以下兩種情況:

  1. 通過給 this.state 賦值對象來初始化內(nèi)部 state瞻颂。
  2. 為事件處理函數(shù)綁定實(shí)例。
    也就是說郑象,組件中的 construtor完全可以省略贡这,但一旦寫了,然后super里沒有傳遞props厂榛,就會出現(xiàn) constructor 里的 this.props 丟失的情況盖矫,但是可以直接訪問 props
class RC extends React.Component {
    constructor(props) {
        super()
        console.log(this.props);        //這里輸出 undefined
        console.log(props);     //這里輸出props
    }
    render() {
        return <div></div>
    }
}
ReactDOM.render(<RC name="ming" />, document.getElementById('app'))

ref 與 refs

ref 是聲明在DOM標(biāo)簽上的,refs是用于讀取 ref 列表

class RC extends React.Component {
    render() {
        return (
            <div>
                <input type="text" ref="input1" />
                <button onClick={this.btnClick}>點(diǎn)我看東西</button>
                <input onBlur={this.handlerBlur} type="text" ref="input2" />
            </div>
        )
    }
    btnClick = () => {
        this.refs.input2.value = this.refs.input1.value
    }
    handlerBlur = () => {
        alert(this.refs.input2.value)
    }
}
ReactDOM.render(<RC name="ming" />, document.getElementById('app'))

ref的三種形式

  1. ref 的值為字符串形式:官方不推薦使用击奶,存在一些性能上效率上的問題辈双。
  2. ref 的值為一個回調(diào)函數(shù)形式,取的時(shí)候直接從this身上取而不是 refs 了
render() {
    return (
        <div>
            <input onBlur={ handleBlur } type="text" ref={ currentNode => this.input1=currentNode } />
        </div>
    )
}
handleBlur = ()=>{
    console.log(111)
}

像這種直接在內(nèi)聯(lián)里寫回調(diào)函數(shù)的形式柜砾,在更新過程中會被執(zhí)行兩次湃望。通過在類里面直接定義函數(shù)然后在節(jié)點(diǎn)上使用可以解決這種問題。不過大多時(shí)候無關(guān)緊要

render() {
    return (
        <div>
            <input onBlur={ handleBlur } type="text" ref={ this.currentNode } />
        </div>
    )
}
currentNode = (c) => this.input1=c
  1. 使用 React.createRef() 來創(chuàng)建 ref 標(biāo)識,讀取的時(shí)候從 this.容器名.current 才能讀取到該容器的 DOM
render() {
    return (
        <div>
            <input onBlur={ handleBlur } type="text" ref={ this.myRef } />
        </div>
    )
}
myRef = React.createRef()

綁定事件

給虛擬DOM綁定事件证芭,官方推薦用 onXxx 寫法瞳浦,注意 Xxx 開頭字母需要大寫,例如 onclick 要寫成 onClick

class RC extends React.Component {
    render() {
        return (
            <h1 onClick={handleClick}>點(diǎn)我看控制臺</h1>
        )
    }
}
function handleClick() {
    console.log('我點(diǎn)擊了');
}
ReactDOM.render(<RC />, document.getElementById('app'))

使用 onXxx 寫法原因有二:

  1. React 使用的是自定義事件檩帐,而不是使用的原生 DOM 事件
  2. React 中的事件是通過事件委托方式處理的 (委托給組件最外層的元素)

事件可以通過 event.target 得到發(fā)生事件的 DOM 元素對象

表單數(shù)據(jù)收集

非受控組件

現(xiàn)用現(xiàn)取

受控組件

監(jiān)聽 onChange 事件然后通過 this.setState 設(shè)置狀態(tài)

class RC extends React.Component {
    render() {
        return (
            <div>
                <form onSubmit={this.handleSubmit}>
                    用戶名:<input onChange={this.handlerUsername} type="text" />
                    密碼:<input onChange={this.handlerPassword} type="password" />
                    <button>提交</button>
                </form>
            </div>
        )
    }
    state = {
        username: '',
        password: ''
    }
    handlerUsername = (e) => {
        this.setState({
            username: e.target.value
        })
    }
    handlerPassword = (e) => {
        this.setState({
            password: e.target.value
        })
    }
    handleSubmit = (e) => {
        e.preventDefault()
        const { username, password } = this.state
        console.log(`用戶名是${username}术幔,密碼是${password}`);
    }
}
ReactDOM.render(<RC name="ming" />, document.getElementById('app'))

少用函數(shù)優(yōu)化以上代碼

可以使用函數(shù)柯里化另萤、內(nèi)聯(lián)函數(shù)湃密、靈活運(yùn)用 event 對象等方法優(yōu)化以上代碼

class RC extends React.Component {
    render() {
        return (
            <div>
                <form onSubmit={this.handleSubmit}>
                    用戶名:<input onChange={this.handleChange} type="text" name="username" />
                    密碼:<input onChange={this.handleChange} type="password" name="password" />
                    <button>提交</button>
                </form>
            </div>
        )
    }
    state = {
        username: '',
        password: ''
    }
    handleChange = (e) => {
        const value = target.type === "checkbox"? target.checked:target.value
        this.setState({
            [e.target.name]: value
        })
    }
    handleSubmit = (e) => {
        e.preventDefault()
        const { username, password } = this.state
        console.log(`用戶名是${username},密碼是${password}`);
    }
}
ReactDOM.render(<RC name="ming" />, document.getElementById('app'))

雙向綁定

注意雙向綁定除了要綁定 onChange 事件以外還要把 value 值綁定為state值四敞,否則狀態(tài)改變時(shí)輸入框內(nèi)容不會改變

<input name="username" onChange={this.handleInput} value={this.state.username}></input>

當(dāng)輸入組件的 name 和 state 里面的狀態(tài)值名一致時(shí)就能做到上面的優(yōu)化

React 生命周期

生命周期(舊)

shouldComponentUpdate鉤子默認(rèn)返回真泛源,如果返回值為假,后面的生命周期都不會走
forceUpdate 強(qiáng)制觸發(fā)更新
componentWillReceiveProps 第一次接收props不會觸發(fā)忿危,后面才會觸發(fā)
[圖片上傳失敗...(image-69038a-1658627198688)]

生命周期>16.4

[圖片上傳失敗...(image-3124ce-1658627198688)]

可以看到新的生命周期多了 static getDerivedStateFromProps 和 getSnapshotBeforeUpdate达箍。
實(shí)際上工作中常用的就

static getDerivedStateFromProps

直譯 從props衍生的狀態(tài),使用這個鉤子會影響狀態(tài)更新铺厨,可以接到 props 參數(shù)和 state缎玫。返回值就成為組件的狀態(tài)
通常這么用,官方文檔說用于 state的值任何時(shí)候都取決于 props

static getDerivedStateFromProps(props,state){
    return props
}

getSnapshotBeforeUpdate

直譯:在更新之前獲取快照解滓,必須返回一個 null 或者快照值
該鉤子會在最近一次渲染輸出(提交到DOM節(jié)點(diǎn))之前調(diào)用赃磨。該鉤子的返回值會傳給 componentDidUpdate 的第三個參數(shù)

getSnapshotBeforeUpdate(){
    return 給componentDidUpdate的值
}

componentDidUpdate

該鉤子可以接到兩個參數(shù),一個是更新之前的props洼裤,另一個是更新前的state邻辉,
如果getSnapshotBeforeUpdate return了一個值,就會在第三個參數(shù)中接到

componentDidUpdate(preProps,preState,snapshot){

}

如果要在這個鉤子中使用 setState腮鞍,必須放在一個 if 中

樣式模塊化

在樣式文件中間加上 .module. 例如 index.module.css
在引入的時(shí)候可以起個名字接住這個樣式模塊值骇,使用的時(shí)候用對象點(diǎn)的形式

import hello from './index.module.css'
export default class hello extends Component{
    render(){
        return <h2 className={hello.title}></h2>
    }
}

組件通信

父子:props
子父:父給子傳遞函數(shù),子調(diào)用函數(shù)傳遞參數(shù)
兄弟:第三方庫 pubsub-js

import PubSub from 'pubsub-js'

PubSub.subscribe("消息名",回調(diào)函數(shù)(訂閱名,消息內(nèi)容){})
PubSub.subscribe('userInfo',(_,data)=>{})

PubSub.publish('消息名',消息)

腳手架代理解決跨域

方法一(簡單方式):在 package.json 配置

"proxy":"地址"

方法二:在src目錄下創(chuàng)建 setupProxy.js

const proxy = require('http-proxy-middleware'

module.exports = function (app){
    app.use(
        //新版是proxy.createProxyMiddleware
        proxy('/api1',{
            target: 'http://localhost:5000',
            changeOrigin: true,
            pathRewrite: {'^/api1': ''}
        })
    )
}

使用 pathRewrite 的原因是防止與項(xiàng)目目錄下的資源同名移国,

react 路由

注意2022年路由用的是6版本拧廊,react16舊一點(diǎn)的用5

npm i react-router-dom@5

路由跳轉(zhuǎn)組件,使用前要從庫中引入凶杖,Link組件包在Router組件里面颅围。用to屬性來指定跳轉(zhuǎn)地址

import {Link,Router} from 'react-router-dom'
<Router>
    <Link to="/about"></Link>
</Router>

路由器分為 BrowserRouter 和 HashRouter,直接使用Router會報(bào)錯

import {Link,BrowserRouter} from 'react-router-dom'
<BrowserRouter>
    <Link to="/about"></Link>
</BrowserRouter>

使用 Route 注冊路由裹芝,兩個關(guān)鍵屬性 path 指定展示的組件和 component 指定要展示的組件部逮,然后也要用 Router(BrowserRouter 和 HashRouter) 組件包著

import About from './component/About'
import Home from './component/Home'
<BrowserRouter>
    <Route path="/home" component={Home} />
    <Route path="/about" component={About} />
</BrowserRouter>

而且注意 Link 和 Route 要包在一個 Router 里

<BrowserRouter>
    <Link to="/home"></Link>
    <Link to="/about"></Link>
    <Route path="/home" component={Home} />
    <Route path="/about" component={About} />
</BrowserRouter>

尚硅谷的教程里把 </BrowserRouter> 扔在了 index.js 里寫在了 App 組件外面

組件分類

組件分為路由組件與一般組件,路由用的組件一般不寫在 component 中嫂易,寫在 pages 或 views 里

路由傳參

路由組件默認(rèn)收到4個props參數(shù):history兄朋、location、match、staticcontext
這些參數(shù)有幾個方法和屬性需要關(guān)注
history:go,goBack,goForward,push,replace
location:pathname,search,state
match:params,path,url

一般組件插槽

<MyComponent>自定義插槽內(nèi)容</MyComponent>
上面的自定義插槽內(nèi)容傳到哪里了呢颅和,在組件內(nèi)部輸出this.props傅事,可以看到掛在了 children 上
console.log(this.props.children) 就會輸出插槽內(nèi)容

NavLink

NavLink 組件會默認(rèn)給活躍的路由組件加上 active 類名,
也可以使用 activeClassName 指定活躍的類名

<NavLInk activeClassName="zidingyi"/>

一個地址匹配多個路由

<Route path="/home" component={Home}>
<Route path="/home" component={About}>

像上面這種情況峡扩,router版本5會都顯示

Switch組件

Switch組件可以防止上面的情況出現(xiàn)蹭越,匹配到了一個路由就不會往下匹配

import {Switch,Route} from 'react-router-dom'
<Switch>
    <Route path="/home" component={Home}>
    <Route path="/home" component={About}>
</Switch>

多級路由資源丟失問題

低版本路由存在多級路由資源丟失的問題,解決方法有3

  1. 使用絕對路徑
  2. 使用 %PUBLIC_URL% 代替絕對路徑
  3. 使用 HashRouter

但是 HashRouter 會造成 刷新后 state 參數(shù)的丟失教届,后面會講

路由模糊匹配與精準(zhǔn)匹配

Link 組件的 to 路徑會從右往左進(jìn)行模糊匹配响鹃,如果匹配上了會展示模糊匹配的組件,如果從左開始沒命中則不展示案训。
如果要使用精準(zhǔn)匹配在 Route 組件中使用 exact 屬性開啟精準(zhǔn)匹配

<Route exact></Route>

路由重定向 Redirect 兜底組件

<Route path="/home" component={Home}>
<Route path="/about" component={About}>
<Redirect to="/about">

這個組件指如果所有的Route組件都匹配不上則走重定向組件

多級路由/嵌套路由

一級路由Link完成注冊以及Route準(zhǔn)備好展示之后买置,需要在子頁面上繼續(xù)注冊和準(zhǔn)備展示。

路由傳參

路由傳遞 params 參數(shù)

需要在 Link 在 to 用模板字符串傳遞參數(shù)强霎,然后在 Route 的 path 用冒號占位

<Link to={`/home/msg/${x1}`}></Link>
<Route path="home/msg/:id"/>

然后在路由組件身上的 this.props.match 上就會找到 params

傳遞 search 參數(shù)

傳遞 search 參數(shù)則無需在 Route 組件上聲明接受忿项,直接去組件身上的
this.props.location.search

<Link to={"home/msg?id=1"></Link>

這種 urlencoded 參數(shù)可以借助庫來拆解為對象或字符串,例如 node 自帶的 qs 庫

傳遞 state 參數(shù)

傳遞 state 參數(shù)城舞,Link 組件里的 to就要寫成對象形式

<Link to={{
    pathname:'/home/message',
    state:{
        id:'1',
        name:'ming'
    }
}}></LInk>

然后去組件的 this.props.location.state 上取轩触,這種參數(shù)在 BrowserRouter 方式的路由上刷新不會丟失,清空瀏覽器歷史記錄才會丟失

push 與 replace

路由開啟 replace 模式家夺,只需要在 Link 組件中聲明 replace 屬性即可脱柱。

<Link replace></Link>
或
<Link replace={true}></Link>

編程式路由

在 Link 組件里面的元素,身上的 props 都具有路由組件的方法秦踪,調(diào)用這些方法可以實(shí)現(xiàn)編程式路由
this.props.history.push()this.props.history.replace()
傳遞 params 參數(shù)和 search 都是一樣的寫法褐捻,傳遞 state 參數(shù)則是直接在地址后面接一個對象

this.props.history.push('/home/msg',{id,name})

然后 this.props.history.go() goBack() goFoward() 就是前進(jìn)后退了

一般組件使用編程式路由導(dǎo)航 withRouter

使用 withRouter 包住一般組件可以讓一般組件也有 history,location 這些東西

import {withRouter} from 'react-router-dom'
class MyComponent extends Component{}

export default withRouter(MyComponent)

狀態(tài)管理 redux

redux 三大核心概念:action creators椅邓,store柠逞,reducers

action

action就是個對象,包含兩個屬性:

  1. type:值為字符串景馁,必要屬性
  2. data:任意類型板壮,可選屬性
    {type:'ACT_ADD',data:{name:'ming'}}
    store.dispatch() 分發(fā)action

reducer

  1. 用于初始化狀態(tài)和加工狀態(tài)
  2. 加工時(shí),根據(jù)舊的 state 和 action合住,產(chǎn)生新的 state 純函數(shù)
  3. reducer 函數(shù)會接到兩個參數(shù)绰精,分別為之前的狀態(tài)和動作對象

store

組件中讀取 store 的數(shù)據(jù)使用 getState() 方法

創(chuàng)建 redux/store.js

import { createStore } from "redux";
// import { legacy_createStore as creatStore} from "redux";
import countReducer from './count_reducer'
export default createStore(countReducer)

創(chuàng)建 reducer


const initState = 0
export default function countReducer(preState = initState, action) {
    console.log(preState);
    const { type, data } = action
    switch (type) {
        case 'increment':
            return preState + data
        case 'decrement':
            return preState - data
        default:
            return preState
    }
}


組件中使用store

import store from '@/redux/store'

//讀取用 getState()

//分發(fā)action用dispatch

注意 redux 只管理狀態(tài)不更新頁面,使用 store.subscribe(()=>{}) 即可透葛,只要redux狀態(tài)變化笨使,就會調(diào)用回調(diào)

componentDidMount(){
    store.subscribe(()=>{
        this.setState({})
    })
}

或者直接在 index.js 使用

store.subscribe(()=>{
    ReactDOM.render(<App/>,document.getElementById('root'))
})

創(chuàng)建專門文件管理 action

redux/xxx_action.js

export const createIncrementAction = (data)=>{
    return ({
        type:'increment',
        data
    })
}

創(chuàng)建專門文件管理redux變量常量

redux/constant.js

export INCREMENT = 'increment'

異步 action

import store from "./store"
export const asyncPlus = (data) => {
    return () => {
        setTimeout(() => {
            store.dispatch({ type: 'increment', data })
        }, 500)
    }
}

借助 redux-thunk 中間件實(shí)現(xiàn)
在store.js里

import {createStore,applyMiddleware} from 'redux'
import thunk 'redux-thunk'

import countReducer from './count_reducer'

export default createStore(countReducer,applyMiddleware(thunk))

當(dāng)然異步 action 不是必須的,完全可以自己等待異步任務(wù)的結(jié)果再去派發(fā)同步 action

react-redux

npm i react-redux

react-redux 多了一層 UI組件和容器組件的概念僚害,UI組件不能直接與 redux 進(jìn)行交互硫椰,只能與容器組件通過 props 進(jìn)行交互,而容器組件才能進(jìn)行 store.getState() 等操作

創(chuàng)建容器組件

//引入 UI 組件
import CountUI from '@/compoents/Count'

//引入 store
import store from '@/redux/store'

//引入connect用于鏈接UI組件與redux
import {connect} from 'react-redux'

//a函數(shù)作為返回的對象中key就作為傳遞給UI組件props的key,value就作為傳遞給UI組件props的value——狀態(tài)靶草,會接到一個形參是redux傳過來的state
function a(state){
    return {count:state}
}
//b函數(shù)作為返回的對象中key就作為傳遞給UI組件props的key蹄胰,value就作為傳遞給UI組件props的value——操作狀態(tài)的方法
//b函數(shù)能接到redux傳過來的dispatch。返回的函數(shù)接到形參是UI組件傳過來的數(shù)據(jù)data
function b(dispatch){
    return {jia:(number)={
        dispatch({type:'increment',data:number})
    }}
}

//使用 connect()() 創(chuàng)建并暴露一個 Count 的容器組件奕翔,第一個括號用于傳遞狀態(tài)和操作狀態(tài)的方法裕寨,官方名稱叫mapStateToProps 和 mapDispatchToProps
export default connect(a,b)(CountUI)

此時(shí)注意寫好容器組件后,掛在頁面上的就是容器組件而不是UI組件派继,并且要通過props傳遞store

import Count from './containers/Count'
import store from '@/redux/store'

export default class App extends Component{
    render(){
        return (
        <div>
            <Count store={store}/>
        </div>
        )
    }
}

mapDispatchToProps簡寫

原寫法是像上面那樣一個接到dispatch的函數(shù)宾袜,簡寫攜程一個對象,react-redux自動幫你dispatch

export default connect(
state=>({count:state}),
{
    jia:createIncrementAction,
    jian:createDecrementAction
}
)

Provider組件

我們在使用容器組件的時(shí)候都需要手動傳一個 store 進(jìn)去互艾,如果不傳則會報(bào)錯试和,這時(shí)可以借助 Provider 組件讯泣,同時(shí)注意 react-redux 不需要自己手動更新 render 了
在 index.js 中

import {Provider} from 'react-redux'

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

combineReducers 管理多個模塊狀態(tài)

最終react-redux容器組件

import React, { Component } from 'react'
import { connect } from 'react-redux'
import { createIncrementAction } from '@/redux/count_action'
class Count extends Component {
    render() {
        return (
            <div>
                <h1>當(dāng)前求和為:{this.props.he}</h1>
                <button onClick={this.add}>點(diǎn)我加</button>
            </div>
        )
    }
    add = () => {
        this.props.jiafa(1)
    }
}
export default connect(
    state => ({
        he: state,
    }),
    {
        jiafa: createIncrementAction
    }
)(Count)

setState 拓展

setState 寫法一

this.setState()會接受兩個參數(shù)纫普,第一個參數(shù)是狀態(tài)改變對象,第二個參數(shù)是狀態(tài)更新視圖也女性g之后會調(diào)用的回調(diào)

this.setState({num:1},()=>{
    console.log(this.state.num)
})
console.log(this.state.num)     //這里是讀不到的

因?yàn)閞eact更新視圖是在異步隊(duì)列進(jìn)行的好渠,如果在 setState 后面直接讀取一些信息會讀不到

setState 寫法二

寫法二的第一個參數(shù)寫成一個函數(shù)昨稼,該函數(shù)會接到兩個形參,state和props拳锚。

this.setState((state,props)=>{
    return {num:state.num+1}
},()=>{

})

如果新狀態(tài)依賴于原狀態(tài)假栓,推薦使用寫法二函數(shù)式。
第二個參數(shù)是一個回調(diào)函數(shù)霍掺,該回調(diào)會在 state 更新完后執(zhí)行匾荆。

setState 其他說明

一次函數(shù)中多次調(diào)用 setState,只會出發(fā)一次重新渲染

路由懶加載

從 react 上引入 lazy 使用杆烁,需要搭配 Suspense 指定懶加載替換組件牙丽,替換組件不能懶加載

import React,{Compoent,lazy} from 'react'

Hook/Hooks

useState

react 16.8 之前,函數(shù)式組件沒有 state兔魂,refs 等烤芦,函數(shù)式組件連自己的this都沒有。有了 hooks 后析校,函數(shù)式組件就可以做很多東西了构罗。但同時(shí)注意,函數(shù)式組件的render要放最后智玻,不然會出現(xiàn)奇怪的bug遂唧。

import React from 'react'
export default function Count() {
    // const [狀態(tài)名,修改狀態(tài)的方法] = React.useState(初始值)
    const [count, setCount] = React.useState(0)
    function add() {
        // setCount(count + 1)
        setCount((value) => {
            return value + 1
        })
    }
    return (
        <div>index{count}
            <button onClick={add}>+</button>
        </div>
    )
}

修改狀態(tài)的方法可以接受兩種參數(shù),第一種就是直接寫值吊奢,第二種寫成回調(diào)函數(shù)形式盖彭,形參可以接收到原來的值

useEffect

React.useEffect(()=>{},[])

useEffect 可以接收到兩個參數(shù),第一個參數(shù)是回調(diào)函數(shù),該回調(diào)會在第二個數(shù)組里監(jiān)測的數(shù)據(jù)改變后執(zhí)行谬泌,第二個參數(shù)是數(shù)組滔韵,里面的數(shù)據(jù)會影響第一個參數(shù)的回調(diào)函數(shù)執(zhí)行。
如果不寫第二個參數(shù)掌实,就相當(dāng)于監(jiān)視所有數(shù)據(jù)的變化陪蜻。

使用 effect hook 模擬生命周期

當(dāng)?shù)诙€參數(shù)的數(shù)組里不寫東西,就相當(dāng)于寫了一個 componentDidMount 生命周期贱鼻。
當(dāng)?shù)谝粋€參數(shù)返回一個函數(shù)宴卖,這個返回的函數(shù)就相當(dāng)于一個 componentWill

useRef

類似 createRef

import React from 'react'
export default function Count() {
    // const [狀態(tài)名,修改狀態(tài)的方法] = React.useState(初始值)
    const myRef = React.useRef()
    const logVal = () => {
        console.log(myRef.current.value);
    }
    return (
        <div>
            <input type="text" ref={myRef} />
            <button onClick={logVal}></button>
        </div>
    )
}

Fragment 與 空標(biāo)簽

組件必須有一個根標(biāo)簽,此時(shí)可以借助 Fragment 組件或根標(biāo)簽來解決
但是注意 Fragment 除了key以外不能寫其他屬性邻悬,空標(biāo)簽則不能加其他屬性

context

創(chuàng)建 Context 容器對象:const XxxContext = React.createContext()
渲染子組件時(shí)症昏,外面包裹 xxxContext.Provider,通過 value 屬性給后代組件傳遞數(shù)據(jù):

<XxxContext.Provider value={數(shù)據(jù)}>
子組件
</XxxContext.Provider>

后代組件讀取數(shù)據(jù):

//第一種方式:類似逐漸
static contextTyoe = XxxContext
this.context

//第二種方式父丰,函數(shù)組件與類組件都可以
<xxxContext.Consumer>
{
    value=>(
        要顯示內(nèi)容de
    )
}
<xxxContext.Consumer>

通常開發(fā)不會使用context肝谭,只會用來開發(fā)插件。
注意是 React.createContext() 調(diào)用后取到的組件蛾扇,不然會被報(bào)錯卡住

import React, { useState } from 'react'
const { Provider, Consumer } = React.createContext()
export default function Hello(props) {
    const [count] = useState(2)
    return (
        <Provider value={count}>
            <Acompo />
        </Provider>
    )
}
const Acompo = (props) => {
    return (
        <Bcompo />
    )
}
const Bcompo = (props) => {
    return (
        <Consumer>
            {data => (
                <>{data}</>
            )}
        </Consumer>
    )
}

PureComponent

Component的兩個問題

  1. 只要執(zhí)行 setState() 即使不改變狀態(tài)數(shù)據(jù)攘烛,組件也會重新 render()
  2. 只要當(dāng)前組件重新 render() ,就會自動重新 render 子組件(效率低)
    想要效率高镀首,那就是只有當(dāng)組件的 state 或 props 時(shí)才重新 render

原因

組件中的 shouldComponentUpdate() 總是返回true坟漱,該鉤子可以接收到兩個形參,nextProps 和 nextState

shouldComponentUpdate(nextProps,nextState){
    
}

解決方法之一就是重寫這個鉤子更哄,比較新舊 state 或 props 值芋齿,如果變化了才返回 true,沒有變化則 false

最佳解決方法 PureComponent

PureComponent 原理也是重寫了shouldComponentUpdate成翩,不過注意的是只是對 state 和 props 進(jìn)行淺比較觅捆。

import {PureComponent} from 'react'

export default class MyComponent extends PureComponent{}

renderProps 插槽

如果一個非自閉合組件在標(biāo)簽體內(nèi)寫一般內(nèi)容,可以在 this.children 上讀取到捕传,但如果放進(jìn)去的是 DOM惠拭,同時(shí)還要不知道放什么子組件但要給子組件傳參時(shí),那么需要借助 render才行

import React, { Component } from 'react'
export default class index extends Component {
    render() {
        return (
            <div>
                <A render={(參數(shù)) => {
                    return <B propname={參數(shù)}></B>
                }} />
            </div>
        )
    }
}
class A extends Component {
    render() {
        return (
            <>
                我是A啊 <br />
                {this.props.render(參數(shù))}
                <br />  我是A最后
            </>
        )
    }
}
class B extends Component {
    render() {
        return (
            <>
                我是b啊
            </>
        )
    }
}

render props 是一種模式庸论,具體怎么用看個人喜歡职辅,也有直接用 props.children 的。本質(zhì)還是通過函數(shù)實(shí)現(xiàn)組件通信聂示,插槽通信域携。此時(shí)最好對 children 進(jìn)行一些校驗(yàn)

復(fù)用組件.propTypes = {
    children: PropTypes.func.isRequired
}

error boundary 錯誤邊界

錯誤邊界只能用于生產(chǎn)環(huán)境中,可以限制錯誤擴(kuò)散鱼喉,不過getDerivedStateFromError不能捕獲自己的錯誤秀鞭,只能捕獲子組件并且只能捕獲到生命周期里的錯誤

import React, { Component } from 'react'
export default class Parent extends Component {
    static getDerivedStateFromError(error) {
        console.log(error);
        return { hasError: error }
    }
    state = {
        hasError: ''
    }
    render() {
        return (
            <div>Parent</div>
            {this.state.hasError?<h2>當(dāng)前網(wǎng)絡(luò)不穩(wěn)定趋观,請稍后再試</h2>:<Child/>}
        )
    }
}

配合 componentDIdCatch(){} 鉤子統(tǒng)計(jì)錯誤

高階組件

高階組件就是一個function,參數(shù)是一個沒有自己狀態(tài)與方法的jsx組件只使用 props 的 jsx 組件锋边。

//需要配合高階組件使用的低階組件
let lowerMouse = (props) => {
    return (
        <img src={img} style={{
            width: 10,
            position: 'absolute',
            top: props.y,
            left: props.x
        }}></img>
    )
}
let LowerMouse = withMouse(lowerMouse)

高階組件一般以 withXXX 命名皱坛,我們來看一下他怎么寫的:
接受一個組件的函數(shù),在 render 里使用豆巨,并且在使用時(shí)候傳遞函數(shù)里的組件的state作為 props剩辟。
這個函數(shù)最終把函數(shù)內(nèi)的組件返回出去,那么接收一個組件作為參數(shù)后就可以為低級組件包裝了往扔。

export default function withMouse(WrappedComponent) {
    class index extends Component {
        state = {
            x: undefined,
            y: undefined
        }
        handleXY = (e) => {
            this.setState({
                x: e.clientX,
                y: e.clientY
            })
        }
        componentDidMount() {
            window.addEventListener('mousemove', this.handleXY)
        }
        componentWillUnmount() {
            window.removeEventListener('mousemove', this.handleXY)
        }
        render() {
            return (
                <WrappedComponent {...this.state} {...this.props}></WrappedComponent >
            )
        }
    }
    //優(yōu)化 設(shè)置displayName
    index.displayName = `WithMouse${getDisplayName(WrappedComponent)}`
    
    return index
}
function getDisplayName(WrappedComponent){
    return WrappedComponent.displayName || WrappedComponent.name || 'Component';
}

優(yōu)化

  1. 設(shè)置displayName優(yōu)化開發(fā)者工具
  2. 添加 props

組件性能優(yōu)化

組件性能優(yōu)化

減輕state:只存儲跟組件渲染相關(guān)的數(shù)據(jù)贩猎,不做渲染的數(shù)據(jù)放在this上即可,例如定時(shí)器的id

React18

ReactDOM.createRoot

將原生DOM作為參數(shù)傳入該方法萍膛,調(diào)用后返回值就可以掛載虛擬DOM吭服,參數(shù)內(nèi)的所有內(nèi)容都會被清空

const div = React.createRoot(document.getElementById('root'))
root.render(<App/>)

react-dom/client

18之后引入 ReactDOM 不是從 react-dom 上引入而是

import ReactDOM from 'react-dom/client'

useState()

需要一個值作為參數(shù)作為該state的初始值,該函數(shù)會返回一個數(shù)組蝗罗,
數(shù)組第一個元素是初始值艇棕,直接修改不會出發(fā)組件的重新渲染。
數(shù)組的第二個元素是一個函數(shù)绿饵,通常命名為setXXX欠肾,用來修改state瓶颠,調(diào)用其修改state后會觸發(fā)組件更新拟赊。
該函數(shù)的參數(shù)就會作為新的值賦值給該state

import {useState} from 'react'
let result = useState(1)
let count = result[0]
let setCount = result[1]
setCount()

當(dāng) setState 調(diào)用的時(shí)候使用舊的state值一定要注意,很可能會出現(xiàn)計(jì)算錯誤的情況粹淋,因?yàn)楦?state 是異步進(jìn)行的吸祟。這種時(shí)候可以使用回調(diào)函數(shù)來進(jìn)行setState,回調(diào)函數(shù)會接到原來的數(shù)值作為參數(shù)桃移,返回值將作為新值

setCount((preValue)=>{
    return preValue+1
})

useRef()

該鉤子只能在函數(shù)組件中直接使用屋匕,不要在嵌套的函數(shù)中使用

const myRef = useRef()
return(
    <input ref={myRef}/>
)

注意讀出組件是從 myRef.current 上讀不是直接讀 myRef。
其實(shí)可以直接寫一個普通對象代替 useRef()const myRef = {current:undefined}
不過普通對象會在組件每次重新渲染時(shí)創(chuàng)建一個新對象借杰,但 useRef 不會

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末过吻,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子蔗衡,更是在濱河造成了極大的恐慌纤虽,老刑警劉巖,帶你破解...
    沈念sama閱讀 206,378評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件绞惦,死亡現(xiàn)場離奇詭異逼纸,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)济蝉,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,356評論 2 382
  • 文/潘曉璐 我一進(jìn)店門杰刽,熙熙樓的掌柜王于貴愁眉苦臉地迎上來菠发,“玉大人,你說我怎么就攤上這事贺嫂∽茵” “怎么了?”我有些...
    開封第一講書人閱讀 152,702評論 0 342
  • 文/不壞的土叔 我叫張陵第喳,是天一觀的道長哥力。 經(jīng)常有香客問我,道長墩弯,這世上最難降的妖魔是什么吩跋? 我笑而不...
    開封第一講書人閱讀 55,259評論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮渔工,結(jié)果婚禮上锌钮,老公的妹妹穿的比我還像新娘。我一直安慰自己引矩,他們只是感情好梁丘,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,263評論 5 371
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著旺韭,像睡著了一般氛谜。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上区端,一...
    開封第一講書人閱讀 49,036評論 1 285
  • 那天值漫,我揣著相機(jī)與錄音,去河邊找鬼织盼。 笑死杨何,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的沥邻。 我是一名探鬼主播危虱,決...
    沈念sama閱讀 38,349評論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼唐全!你這毒婦竟也來了埃跷?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 36,979評論 0 259
  • 序言:老撾萬榮一對情侶失蹤邮利,失蹤者是張志新(化名)和其女友劉穎弥雹,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體近弟,經(jīng)...
    沈念sama閱讀 43,469評論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡缅糟,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,938評論 2 323
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了祷愉。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片窗宦。...
    茶點(diǎn)故事閱讀 38,059評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡赦颇,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出赴涵,到底是詐尸還是另有隱情媒怯,我是刑警寧澤,帶...
    沈念sama閱讀 33,703評論 4 323
  • 正文 年R本政府宣布髓窜,位于F島的核電站扇苞,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏寄纵。R本人自食惡果不足惜鳖敷,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,257評論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望程拭。 院中可真熱鬧定踱,春花似錦、人聲如沸恃鞋。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,262評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽恤浪。三九已至畅哑,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間水由,已是汗流浹背荠呐。 一陣腳步聲響...
    開封第一講書人閱讀 31,485評論 1 262
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留绷杜,地道東北人直秆。 一個月前我還...
    沈念sama閱讀 45,501評論 2 354
  • 正文 我出身青樓,卻偏偏與公主長得像鞭盟,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子瑰剃,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,792評論 2 345

推薦閱讀更多精彩內(nèi)容