React

初始化React

從 html 開始你的第一個 react 頁面。引入三個文件:react核心文件、react-dom文件和轉化jsx的babel。
然后聲明一個變量,變量的值是一個不帶引號的標簽,最后使用 ReactDOM.render 方法把該變量渲染到頁面上。

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

創建虛擬DOM的兩種方式

第一種:JSX語法

現階段需借助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

此方法僅供了解,后續也不會使用

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

參數一,元素類型。參數二,配置對象,用于寫元素屬性。參數三,元素的內容

關于 虛擬DOM

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

創建出來后打印臺輸出可以看到 VDOM 本質上就是一個 object 類型的變量,身上帶有自身特有的虛擬DOM屬性。
與真實 DOM 比較可以發現 react 虛擬 DOM 很輕量

JSX 語法規則

  1. 虛擬DOM只能有一個根標簽。
  2. 虛擬DOM中每一個標簽都必須閉合,單標簽例如 input 也要寫成自閉合形式。
  3. JSX 標簽變量不能帶引號。
  4. 虛擬DOM中如果想使用JS表達式,需要包裹在花括號里面。
  5. 虛擬DOM中書寫樣式的兩個注意點
    1. 如果虛擬 DOM 中想要帶上 class ,注意要寫成 className,原因是跟ES6的class關鍵字沖突了。
    2. 如果虛擬 DOM 中想要寫內聯樣式,style后面的值不能接字符串,需要接一個 JS 表達式,表達式里面是對象形式。
  6. 虛擬DOM中的標簽如果是小寫字母開頭的標簽,JSX 默認解析為 html 原生標簽。
    虛擬DOM中的標簽如果是大寫字母開頭的標簽,JSX 默認解析為 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 渲染可迭代數據為DOM

使用JS表達式的形式渲染可迭代數據,例如用 map 渲染數組,map 的回調函數 return 虛擬DOM,且必須攜帶 key 方便react執行diff算法。

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

組件

在使用 ReactDOM.render() 方法的時候,第一個參數傳入一個標簽,就會尋找與標簽同名的 function 或 class,進而去創建組件實例。

函數式組件(簡單組件)

function MyComponent(){
    return <h2>我是react函數式組件,我的this指向為嚴格模式下的js的undefined</h2>
}
ReactDOM.render(<MyComponent/>,document.querySelector('#app'))

類式組件(復雜組件)

類式組件式繼承 React.Component 父類的之類,必須帶有 render() 方法,render中的 this 指向 組件實例對象。
組件實例對象會在調用 ReactDOM.render 時候自動創建出來。

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

組件的三大屬性

state、props、refs

state

往組件身上添加狀態,需要在組件類里的 constructor 里寫上 this.state={}
注意在添加state之前必須調用 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實例上的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

上面的例子可以看到頁面并沒有更新,那是因為直接修改state的話不是響應式的,需要調用 setState 修改狀態,才能刷新 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寫法很麻煩,大多數時候并不需要用到類本身,而是用類的實例對象。
在class中直接寫賦值語句可以為實例對象直接增加屬性,用于事件觸發的回調函數也可以寫成箭頭函數賦值給變量的形式(因為要讓函數里的this指向組件實例)

class RC extends React.Component {
    render() {
        const { name, age } = this.state
        return (
            <div>
                <h1>我叫{name},我今年{age}歲</h1>
                <button onClick={this.handleClick}>點我增加歲數</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

通過渲染組件時往組件身上寫屬性和屬性值可以往組件內部傳遞數據,通過 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 批量傳遞

可以傳遞一個對象,然后用花括號里面展開對象的形式進行 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傳遞的值進行類型限制或必要性限制,需要借助另一個 react 庫 prop-type。
通過往組件身上添加 propTypes 屬性進行類型限制和必要性限制,添加 defaultProps 進行默認屬性值設置。

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'))

如果要在限制對象內使用進一步限制,使用 PropTypes.shape 方法

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

props 限制的簡寫形式

上面的代碼,可以使用 static 關鍵字優化一下

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'
    }
}

函數式組件中的 props

函數式組件中,在形參中的第一個參數,就是props,當然也可以使用 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 注意點

  1. props 是只讀的,單向數據流保持數據的可追溯性

construtor

官方文檔:通常,在 React 中,構造函數僅用于以下兩種情況:

  1. 通過給 this.state 賦值對象來初始化內部 state。
  2. 為事件處理函數綁定實例。
    也就是說,組件中的 construtor完全可以省略,但一旦寫了,然后super里沒有傳遞props,就會出現 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標簽上的,refs是用于讀取 ref 列表

class RC extends React.Component {
    render() {
        return (
            <div>
                <input type="text" ref="input1" />
                <button onClick={this.btnClick}>點我看東西</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 的值為一個回調函數形式,取的時候直接從this身上取而不是 refs 了
render() {
    return (
        <div>
            <input onBlur={ handleBlur } type="text" ref={ currentNode => this.input1=currentNode } />
        </div>
    )
}
handleBlur = ()=>{
    console.log(111)
}

像這種直接在內聯里寫回調函數的形式,在更新過程中會被執行兩次。通過在類里面直接定義函數然后在節點上使用可以解決這種問題。不過大多時候無關緊要

render() {
    return (
        <div>
            <input onBlur={ handleBlur } type="text" ref={ this.currentNode } />
        </div>
    )
}
currentNode = (c) => this.input1=c
  1. 使用 React.createRef() 來創建 ref 標識,讀取的時候從 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}>點我看控制臺</h1>
        )
    }
}
function handleClick() {
    console.log('我點擊了');
}
ReactDOM.render(<RC />, document.getElementById('app'))

使用 onXxx 寫法原因有二:

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

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

表單數據收集

非受控組件

現用現取

受控組件

監聽 onChange 事件然后通過 this.setState 設置狀態

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'))

少用函數優化以上代碼

可以使用函數柯里化、內聯函數、靈活運用 event 對象等方法優化以上代碼

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值,否則狀態改變時輸入框內容不會改變

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

當輸入組件的 name 和 state 里面的狀態值名一致時就能做到上面的優化

React 生命周期

生命周期(舊)

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

生命周期>16.4

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

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

static getDerivedStateFromProps

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

static getDerivedStateFromProps(props,state){
    return props
}

getSnapshotBeforeUpdate

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

getSnapshotBeforeUpdate(){
    return 給componentDidUpdate的值
}

componentDidUpdate

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

componentDidUpdate(preProps,preState,snapshot){

}

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

樣式模塊化

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

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

組件通信

父子:props
子父:父給子傳遞函數,子調用函數傳遞參數
兄弟:第三方庫 pubsub-js

import PubSub from 'pubsub-js'

PubSub.subscribe("消息名",回調函數(訂閱名,消息內容){})
PubSub.subscribe('userInfo',(_,data)=>{})

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

腳手架代理解決跨域

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

"proxy":"地址"

方法二:在src目錄下創建 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 的原因是防止與項目目錄下的資源同名,

react 路由

注意2022年路由用的是6版本,react16舊一點的用5

npm i react-router-dom@5

路由跳轉組件,使用前要從庫中引入,Link組件包在Router組件里面。用to屬性來指定跳轉地址

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

路由器分為 BrowserRouter 和 HashRouter,直接使用Router會報錯

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

使用 Route 注冊路由,兩個關鍵屬性 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 里

路由傳參

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

一般組件插槽

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

NavLink

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

<NavLInk activeClassName="zidingyi"/>

一個地址匹配多個路由

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

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

Switch組件

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

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 參數的丟失,后面會講

路由模糊匹配與精準匹配

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

<Route exact></Route>

路由重定向 Redirect 兜底組件

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

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

多級路由/嵌套路由

一級路由Link完成注冊以及Route準備好展示之后,需要在子頁面上繼續注冊和準備展示。

路由傳參

路由傳遞 params 參數

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

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

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

傳遞 search 參數

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

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

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

傳遞 state 參數

傳遞 state 參數,Link 組件里的 to就要寫成對象形式

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

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

push 與 replace

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

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

編程式路由

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

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

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

一般組件使用編程式路由導航 withRouter

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

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

export default withRouter(MyComponent)

狀態管理 redux

redux 三大核心概念:action creators,store,reducers

action

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

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

reducer

  1. 用于初始化狀態和加工狀態
  2. 加工時,根據舊的 state 和 action,產生新的 state 純函數
  3. reducer 函數會接到兩個參數,分別為之前的狀態和動作對象

store

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

創建 redux/store.js

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

創建 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()

//分發action用dispatch

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

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

或者直接在 index.js 使用

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

創建專門文件管理 action

redux/xxx_action.js

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

創建專門文件管理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 中間件實現
在store.js里

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

import countReducer from './count_reducer'

export default createStore(countReducer,applyMiddleware(thunk))

當然異步 action 不是必須的,完全可以自己等待異步任務的結果再去派發同步 action

react-redux

npm i react-redux

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

創建容器組件

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

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

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

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

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

此時注意寫好容器組件后,掛在頁面上的就是容器組件而不是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的函數,簡寫攜程一個對象,react-redux自動幫你dispatch

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

Provider組件

我們在使用容器組件的時候都需要手動傳一個 store 進去,如果不傳則會報錯,這時可以借助 Provider 組件,同時注意 react-redux 不需要自己手動更新 render 了
在 index.js 中

import {Provider} from 'react-redux'

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

combineReducers 管理多個模塊狀態

最終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>當前求和為:{this.props.he}</h1>
                <button onClick={this.add}>點我加</button>
            </div>
        )
    }
    add = () => {
        this.props.jiafa(1)
    }
}
export default connect(
    state => ({
        he: state,
    }),
    {
        jiafa: createIncrementAction
    }
)(Count)

setState 拓展

setState 寫法一

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

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

因為react更新視圖是在異步隊列進行的,如果在 setState 后面直接讀取一些信息會讀不到

setState 寫法二

寫法二的第一個參數寫成一個函數,該函數會接到兩個形參,state和props。

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

})

如果新狀態依賴于原狀態,推薦使用寫法二函數式。
第二個參數是一個回調函數,該回調會在 state 更新完后執行。

setState 其他說明

一次函數中多次調用 setState,只會出發一次重新渲染

路由懶加載

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

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

Hook/Hooks

useState

react 16.8 之前,函數式組件沒有 state,refs 等,函數式組件連自己的this都沒有。有了 hooks 后,函數式組件就可以做很多東西了。但同時注意,函數式組件的render要放最后,不然會出現奇怪的bug。

import React from 'react'
export default function Count() {
    // const [狀態名,修改狀態的方法] = 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>
    )
}

修改狀態的方法可以接受兩種參數,第一種就是直接寫值,第二種寫成回調函數形式,形參可以接收到原來的值

useEffect

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

useEffect 可以接收到兩個參數,第一個參數是回調函數,該回調會在第二個數組里監測的數據改變后執行,第二個參數是數組,里面的數據會影響第一個參數的回調函數執行。
如果不寫第二個參數,就相當于監視所有數據的變化。

使用 effect hook 模擬生命周期

當第二個參數的數組里不寫東西,就相當于寫了一個 componentDidMount 生命周期。
當第一個參數返回一個函數,這個返回的函數就相當于一個 componentWill

useRef

類似 createRef

import React from 'react'
export default function Count() {
    // const [狀態名,修改狀態的方法] = 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 與 空標簽

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

context

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

<XxxContext.Provider value={數據}>
子組件
</XxxContext.Provider>

后代組件讀取數據:

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

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

通常開發不會使用context,只會用來開發插件。
注意是 React.createContext() 調用后取到的組件,不然會被報錯卡住

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. 只要執行 setState() 即使不改變狀態數據,組件也會重新 render()
  2. 只要當前組件重新 render() ,就會自動重新 render 子組件(效率低)
    想要效率高,那就是只有當組件的 state 或 props 時才重新 render

原因

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

shouldComponentUpdate(nextProps,nextState){
    
}

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

最佳解決方法 PureComponent

PureComponent 原理也是重寫了shouldComponentUpdate,不過注意的是只是對 state 和 props 進行淺比較。

import {PureComponent} from 'react'

export default class MyComponent extends PureComponent{}

renderProps 插槽

如果一個非自閉合組件在標簽體內寫一般內容,可以在 this.children 上讀取到,但如果放進去的是 DOM,同時還要不知道放什么子組件但要給子組件傳參時,那么需要借助 render才行

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

render props 是一種模式,具體怎么用看個人喜歡,也有直接用 props.children 的。本質還是通過函數實現組件通信,插槽通信。此時最好對 children 進行一些校驗

復用組件.propTypes = {
    children: PropTypes.func.isRequired
}

error boundary 錯誤邊界

錯誤邊界只能用于生產環境中,可以限制錯誤擴散,不過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>當前網絡不穩定,請稍后再試</h2>:<Child/>}
        )
    }
}

配合 componentDIdCatch(){} 鉤子統計錯誤

高階組件

高階組件就是一個function,參數是一個沒有自己狀態與方法的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 命名,我們來看一下他怎么寫的:
接受一個組件的函數,在 render 里使用,并且在使用時候傳遞函數里的組件的state作為 props。
這個函數最終把函數內的組件返回出去,那么接收一個組件作為參數后就可以為低級組件包裝了。

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 >
            )
        }
    }
    //優化 設置displayName
    index.displayName = `WithMouse${getDisplayName(WrappedComponent)}`
    
    return index
}
function getDisplayName(WrappedComponent){
    return WrappedComponent.displayName || WrappedComponent.name || 'Component';
}

優化

  1. 設置displayName優化開發者工具
  2. 添加 props

組件性能優化

組件性能優化

減輕state:只存儲跟組件渲染相關的數據,不做渲染的數據放在this上即可,例如定時器的id

React18

ReactDOM.createRoot

將原生DOM作為參數傳入該方法,調用后返回值就可以掛載虛擬DOM,參數內的所有內容都會被清空

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

react-dom/client

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

import ReactDOM from 'react-dom/client'

useState()

需要一個值作為參數作為該state的初始值,該函數會返回一個數組,
數組第一個元素是初始值,直接修改不會出發組件的重新渲染。
數組的第二個元素是一個函數,通常命名為setXXX,用來修改state,調用其修改state后會觸發組件更新。
該函數的參數就會作為新的值賦值給該state

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

當 setState 調用的時候使用舊的state值一定要注意,很可能會出現計算錯誤的情況,因為更新 state 是異步進行的。這種時候可以使用回調函數來進行setState,回調函數會接到原來的數值作為參數,返回值將作為新值

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

useRef()

該鉤子只能在函數組件中直接使用,不要在嵌套的函數中使用

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

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

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 227,533評論 6 531
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,055評論 3 414
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 175,365評論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,561評論 1 307
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,346評論 6 404
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 54,889評論 1 321
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 42,978評論 3 439
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,118評論 0 286
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,637評論 1 333
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,558評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,739評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,246評論 5 355
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 43,980評論 3 346
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,362評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,619評論 1 280
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,347評論 3 390
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,702評論 2 370

推薦閱讀更多精彩內容