1. 程式人生 > 實用技巧 >React 基礎與實戰

React 基礎與實戰

ReactJs基礎與實戰

前言

剛入前端是先接觸的 React,後面遇到的專案都是 Vue,就把 React 忘了個徹底,直到現在要開始寫 Taro 了~,這篇就是根據當時學習 React 過程記錄下的筆記,因為當時筆記記的非常凌亂,再加上有道雲筆記的目錄非常不友好,所以重新整理成了這篇釋出出來,很久沒寫過,所以如果存在錯誤的地方請留言,我會及時更改~
github地址:ReactJs基礎與實戰.md 喜歡的點個 Star 吧~

環境搭建

npm install -g create-react-app //安裝
create-react-app + 專案名稱 //建立專案
npm start //重啟

設計思想

在React的官方部落格中明確闡述了 React 不是一個 MVC 框架,而是一個用於構建元件化 UI 的庫,是一個前端介面開發工具。所以頂多算是 MVC 中的 V(view)。React 並沒有重複造輪子,而是有很多顛覆性的創新,具體的特性如下:

  • 宣告式的直觀的編碼方式
  • 簡化可複用的元件

JSX寫法

JSX就是 Javascript 和 XML 結合的一種格式。React 發明了 JSX,利用 HTML 語法來建立虛擬 DOM。當遇到<,JSX 就當 HTML 解析,遇到{就當 JavaScript 解析。

import './css/index.css'
class App extends Component {
    render() {
        var myname = 'anna' //不是狀態
        var stylyobj = {
            background: 'red',
            fontSize: '30px'
        }
        //將來 生命週期
        return (
            <div>
                {10 + 20}--{myname}
                {10 > 20 ? 1 : 0}
                <div style={stylyobj}>111111</div>
                <div style={{ background: 'yellow' }}>111111</div>
                // 16版本之前 class 不支援  必須寫成className
                // 16版本之後 class支援 ,但是會報警告
                <div className='active' >33333</div>
                <div id='box' >33333</div>
            </div>
        )
    }
}

元件寫法

1、class 類式元件

import React from 'react'
import { Component} from 'react'
class Hello extends Component {
    render() {
        //將來 生命週期
        return (
            <div>111111
              <ul>
                  <li>1111</li>
                  <li>2222</li>
                    <Child1/>
                    <Child2/>
                    <Child3/>
              </ul>
            </div>
        )
    }
}
//Component ===>React.Component  下面的 可以直接被引入
class Child1 extends Component{
    render(){
        return <div>child1</div>
    }
}

2、function 函式式元件

React16.8 之前, 函式式元件不支援狀態
React16.8 之後, React Hooks 支援狀態和屬性

function Child2(){
    return  (<div>child2
        <span>22222</span>
    </div>)
   
}
const Child3=()=><div>child3</div>
export default Hello

事件的四種寫法

寫法一: 程式碼量少的情況,可直接在標籤裡面寫

// 獲取到輸入框的value值
	 <input type = 'text' ref = 'mytext' />
			<button onClick={() => {
				console.log('onclick', this.refs.mytext)
			}}>add</button>

寫法二方便傳參,改變this執行

{/* 注意這裡不能加小括號 觸發的時候會自動呼叫 。如果加小括號 ,= 函式返回值再呼叫*/}
<button onClick={this.handleAdd2.bind(this)}>add2</button>
	//寫在 render 外面
  handleAdd2(){
        console.log('click22222', this.refs.mytext.value)
    }

寫法三:箭頭函式 無法傳參,但是畢竟方便

<button onClick={this.handleAdd3}>add3</button>
handleAdd3=()=>{
        console.log(this.refs.mytext.value)
    }

寫法四:組合寫法

<button onClick={() => {
                    this.handleAdd3('aaa','bbbb')
                }}>add4</button>
handleAdd3=(x,y)=>{
        console.log(x,y,'click22222', this.refs.mytext.value)
    }

輸入框改變獲取輸入框的值:

				<div>
                <input type="test" onChange={(evt)=>{
                console.log(evt.target.value)
            }}/>

賦值給輸入框

value={this.state.mytext}

改變this指向

bind call apply區別

call:可以傳入多個引數,改變this指向後立刻呼叫函式  
apply:可以傳入陣列 ,改變this指向後立刻呼叫函式  
bind:改變this指向後,可以傳入多個引數,返回的是函式 不會立即呼叫

var obj1={
    name:'obj1',
    getName(){
        console.log(this.name)
    }
}
var obj2 = {
    name: 'obj2',
    getName() {
        console.log(this.name)
    }
}
// obj1.getName()//obj1

// 改變this指向,立即指向方法
// obj1.getName.call(obj2,'aaa','bbbb','cccc')//obj2
//obj1.getName.apply(obj2,['aaa','bbb','ccc'])//obj2

// 改變this指向,但是需要手動執行方法
//obj1.getName.bind(obj2,'aaa','bbb','ccc')()

初始化狀態和修改狀態

狀態(state)

export default class App extends Component {
    state={
        myname:'4567'
    }
    render() {
        return (
            <div>
                {this.state.myname}
            </div>
        )
    }
}

修改狀態(setState)

同步過程

<button onClick={this.handkeClick}>click</button>
   //直接修改狀態
    handkeClick=()=>{
        this.setState({
            myname:'xiaoming',
            myage:'18'
        })
    }

非同步過程

第一種寫法:
接收兩個引數:
第一個引數是物件,修改的狀態值
第二個引數 能夠等待dom樹更新完之後執行


 this.setState({
             myname:'xiaoming'
         },()=>{
             console.log('1',this.state.myname)
         })
之後發生什麼?
//1.虛擬dom建立
//2.diff對比

第二種寫法:
可以獲取到上個狀態值(prevState) 必須有返回值

//1. 簡寫
this.setState((prevState)=>({
            count: prevState.count+1
        }))
// 2. 完整寫法
this.setState((prevState)=>{{
            count: prevState.count+1
        }})

setState 何時同步,何時非同步,為什麼會這樣, React 如何去控制同步非同步?
想了解的可以看這篇React 中setState更新state何時同步何時非同步?

遍歷

寫法一:

{  this.state.datalist.map(item => <li key={item}>{item}</li>) }

寫法二:

 var newlist = this.state.datalist.map(item => <li key={item}>{item}</li>)
 {newlist} //使用變數

通訊

父傳子 通過屬性(props)

父:

//需要 {} 包住才是 js  不然是字串
<Navbar mytitle='home' myshow={false}></Navbar>

子:可以直接通過this.props.屬性名獲取

{this.props.mytitle}

屬性簡寫:

var obj={
            mytitle:'測試',
            myshow:false
        }
  <Navbar {...obj}></Navbar>

屬性驗證

Navbar.propTypes 可以訪問到

    import MyPropTypes from 'prop-types'; //提供驗證資料型別的方法,必須交給MyPropTypes模組方法進行處理驗證
    class Navbar extends Component {
      static propTypes = {
        myshow: MyPropTypes.bool,
      };
    }

預設屬性

static defaultProps = {
        myshow: true
    }

子傳父 通過事件

父:傳了一個回撥函式過去

<Navbar onEvent={() => {
	this.setState({
		isShow: !this.state.isShow
	})
}}></Navbar>

子 :收到這個回撥函式之後 直接呼叫

<button onClick={this.handleClick}>hide/show</button>
handleClick = () => {
	this.props.onEvent()
}

Ref

Ref 你可以用來繫結到 render() 輸出的任何元件上。
這個特殊的屬性允許你引用 render() 返回的相應的支撐例項( backing instance )。這樣就可以確保在任何時間總是拿到正確的例項。
父元件:可一獲取到Input元件的例項,並且對他內部的狀態值進行修改

<Input ref='mytext' />
	<button onClick={
		this.handleClick
	}>add</button>

handleClick = () => {
	console.log(this.refs.mytext.state.mytext)// 拿到值    
	this.refs.mytext.reset() // 清空輸入框
}

子元件

class Input extends Component {
	state = {
		mytext: ''
	}
	reset = () => {
		this.setState({
			mytext: ''
		})
	}
<div>
	<div>others input</div>
	<input value={this.state.mytext} type='text' style={{ background: 'yellow' }} onChange={(ev) => {
		this.setState({
			mytext: ev.target.value
		})

釋出訂閱模式

自己寫一個釋出訂閱模式來傳遞資訊
事件匯流排:用來觀察訂閱者和釋出者,如果發現釋出者傳送了資訊,將資訊立刻傳送給訂閱者

	const EventChannel = {
		list: [],
		subscribe(callback) {
			this.list.push(callback)
		},
		dispatch(data) {
			this.list.forEach(item => {
				item(data)
			})
		}
	}

訂閱者:把自身的回撥儲存在事件匯流排,事件匯流排遍歷呼叫 釋出者呼叫釋出方法可以傳入釋出者自身的引數  訂閱者即可獲取到

	class Child3 extends Component {
	// 建立成功 ,dom掛載完成
	componentDidMount() {
		observer.subscribe((data) => {
			console.log('child3定義的callback', data)
		})
		// console.log('componentDidMount', '呼叫訂閱方法', observer.subscribe())
	}
	render() {
		return <div style={{ background: 'blue' }}>我是微信使用者</div>
	}
}
class Child3 extends Component {
	// 建立成功 ,dom掛載完成
	componentDidMount() {
		observer.subscribe((data) => {
			console.log('child3定義的callback', data)
		})
		// console.log('componentDidMount', '呼叫訂閱方法', observer.subscribe())
	}
	render() {
		return <div style={{ background: 'blue' }}>我是微信使用者</div>
	}
}

釋出者:呼叫事件匯流排的釋出方法
釋出方法:把訂閱者的回撥遍歷出來 然後呼叫 遍歷中間可以傳入自己的引數

class Child2 extends Component {
    render() {
        return <div style={{ background: 'red' }}>公眾號釋出者
        <button onClick={this.handleClick}>釋出</button>
        </div>
    }
    handleClick=()=>{
        EventChannel.dispatch('child2的問候')
    }
}

context 通訊

基地:提供自己的狀態  以及修改狀態的方法
基地程式碼:

export default class App extends Component {
        state = {
            text: '私人服務'
        }
        changeState=(data)=>{
            this.setState({
                text: data
            })
        }
    render() {
        return (
            <GlobalContext.Provider value={{
                sms: '簡訊服務',
                call: '電話服務',
                text: this.state.text,
                changeState:this.changeState
            }}>
                <div>
                    <Child1></Child1>
                </div>
            </GlobalContext.Provider >
        )
    }
}

通訊者:呼叫基地修改的方法,傳入自己的資訊

class Child2 extends Component {
    render() {
        return <GlobalContext.Consumer>
            {context => (
                <div style={{ background: 'blue' }}>child2--{context.call}
                    <button onClick={() => this.handClick(context)}>child2通訊</button>
                </div>
            )
            }
        </GlobalContext.Consumer>
    }
    handClick = (context) => {
        context.changeState('來自child2的問候')
        console.log(context)
    }
}

其他通訊者:一旦狀態改變了 ,可以馬上獲取到

class Child1 extends Component {
    render() {
        return <GlobalContext.Consumer>
            {context => (
                <div style={{ background: 'yellow' }}>child1--{context.text}</div>
            )
            }
        </GlobalContext.Consumer>
    }
}

生命週期

一個元件會按照順序依次經歷以下的三個階段:初始化階段、執行中階段、銷燬階段
其中的三個生命週期即將被廢棄,不建議使用,增加了兩個新的生命週期替代~

初始化階段

componentWillMount

render 之前最後一次修改狀態的機會,在渲染前呼叫,在客戶端也在服務端,
即將被廢棄,不建議使用,17版本之後必須加上 UNSAFE_ 才可以工作(UNSAFE_componentWillMount),
廢棄理由:
在 ssr 中這個方法將會被多次呼叫,所以會重複觸發多遍,同時在這裡如果繫結事件,將無法解綁,導致記憶體洩漏,變得不夠安全高效逐步廢棄

UNSAFE_componentWillMount(){
        console.log('componentWillMount','ajax',)
    }

render

只能訪問 this.props 和 this.state,不允許修改狀態和 dom,在生命週期中會被多次呼叫。

componentDidMount

成功 render 並渲染完成真實 dom 之後觸發,可以修改 dom, 一般請求資料會寫在這個生命週期

 componentDidMount() {
        console.log('componentDidMount', 'ajax 繫結')
        fetch("/test.json").then(res=>res.json()).then(res=>{
            console.log(res.data)
            this.setState({
                list: res.data.films
            })
        })
    }

執行中階段

componentWillReceiveProps

父元件修改屬性觸發(子元件使用) 會走多次 可以在這裡獲取到id
即將被廢棄,不建議使用,17版本之後必須加上 UNSAFE_ 才可以工作(UNSAFE_componentWillReceiveProps)
廢棄理由:
外部元件多次頻繁更新傳入多次不同的props,會導致不必要的非同步請求
這個生命週期有新的生命週期替換—— **getDerivedStateFromProps **  可以看後面的介紹~

   // 會走多次 可以在這裡獲取到id  更新  mount 只會走一次
    UNSAFE_componentWillReceiveProps(nextProps) {
        console.log('componentWillReceiveProps')
        console.log('獲取到ajax資料', nextProps.myname)
    }

shouldComponentUpdate

**返回 false 會阻止 render 呼叫 **
可以做效能調優函式 可以獲取到新的狀態和老的狀態然後對比狀態,如果狀態沒有改變不重新渲染

  shouldComponentUpdate(nextProps, nextState) {
        // 效能調優函式 //新的狀態和老的狀態
        console.log('shouldComponentUpdate',this.state.myname,nextState.myname)
        if (this.state.myname !== nextState.myname){
            return true //返回 true 會自動渲染 這個是手動自己去對比 dom 是否發生改變再去呼叫 render
          // react有自動優化能力 —— PureComponent(看後面~)  
        }
            return false   //false 不會重新渲染
    }

componentWillUpdate

不允許修改屬性和狀態,會被觸發多次,即將被廢
廢棄理由:
更新前記錄 DOM 狀態,可能會做一些處理,與 componentDidUpdate 相隔時間如果太長,會導致狀態不可信,有新的生命週期—— getSnapshotBeforeUpdate 替換

UNSAFE_componentWillUpdate(){
        console.log('componentWillUpdate')
    }

render

只能訪問 this.props 和 this.state,不允許修改狀態和 dom,在生命週期中會被多次呼叫

componentDidUpdate

在元件完成更新後立即呼叫,在初始化時不會被呼叫,可以修改dom

銷燬階段

componentWillUnmount

在元件從 DOM 中移除之前立刻被呼叫,在刪除元件之前進行清理操作,比如計時器和事件監聽器。

新增加的兩個生命週期

getDerivedStateFromProps

同步作用:
1.可以改老狀態  
2.不管是初始化 或者更新  都可以拿到父元件屬性

componentWillReceiveProps(nextProps) //改變才會獲取到屬性
// 裡面不能用this 必須用static  
  static getDerivedStateFromProps(nextprops, state) {  
  //初始化屬性 和更新屬性都可以獲取到
        document.title = nextprops.mytitle
        console.log(nextprops, state)
        return null //狀態不改變
        return {
             myname: state.myname.substring(0, 1).toUpperCase()
         } //返回一個新的狀態  可以在次修改狀態
    }

非同步作用:
在getDerivedStateFromProps 中不可以做ajax請求,必須和其他生命週期配合使用

componentDidMount() {
        console.log('發ajax', this.state.myid)
    }
    // componentWillReceiveProps() {
    //     console.log('發ajax')
    // }
    static getDerivedStateFromProps(nextprops, state) {
        console.log('getDerivedStateFromProps','獲取到id值', nextprops.id)
        return {
            myid:  nextprops.id
        }
    }

getSnapshotBeforeUpdate

可以在更新之前獲取到狀態

   //data 接收到了getSnapshotBeforeUpdate的返回值
    componentDidUpdate(prevProps, prevState,data) {
        console.log('componentDidUpdate', data)
    }
    // 在render 生命之後  在已經更新完之前 可以準確獲取 返回之後的狀態  
    // componentDidUpdate第三個引數可以獲取到這個值
    getSnapshotBeforeUpdate = (prevProps, prevState) => {
        console.log('getSnapshotBeforeUpdate','獲取滾動條的位置')
        return {
            y:100
        }
    }

React 中效能優化的方案

1、shouldComponentUpdate

手動控制元件自身或者子元件是否需要更新,尤其子元件非常多的情況,不適合這個方案

2、PureComponent

PureComponent 是 React 提供的自動優化 ,會幫你比較新的 props 跟 舊的 props ,
取決於值是否相等(值相等,或者物件含有相同的屬性、且屬性值相等),決定 shouldComponentUpdate 返回true 或者 false,從而決定要不要呼叫 render function
優點:可以減少重複生命週期的執行 會自動對比虛擬dom等
缺點:如果你的state 或者 props '永遠都會變',那 PureComponent 並不會更加快,因為 shallowEqual 也需要花時間,元件如果需要實時更新 可以用 shouldComponentUpdate

import React, { Component, PureComponent } from 'react'
export default class App extends PureComponent {}

插槽

this.props.children  獲取到的是內容陣列
Child 元件:

const Child = (props)=>{
    return <div>
        child--{props.children}
    </div>
}

使用這個元件:

export default function App() {
    return (
        <div>
             {/* 插槽 */}
            <Child>
                <li>11111</li>
                <li>222222</li>
            </Child>
        </div>
    )
}

路由

安裝:

 cnpm i  --save  react-router-dom

HashRouter模式下 多次點相同路徑會被警告
解決方案: 換成history模式  : BrowserRouter
寫法:
react-router-dom 4.5 版本寫法一致:

// 
import {
HashRouter as Router, //路由外層需要包裹的元件  hash模式
// BrowserRouter(後端配置 history 模式)
Route ,//每個路由元件都需要此元件
}from 'react-router-dom'
import React from 'react'
import Home from '../views/home/Home'
import Login from '../views/login/Login'
// class BlogRouter extends Comment{
// }

** 函式式元件寫法:**

const BlogRouter=()=>(
    <Router>
    <Route path='/home' component={Home}/>
    <Route path='/login' component={Login}/>
    </Router>
)
export default BlogRouter
app.js
import BlogRouter from './router'
class App extends Component{
  render(){
    return(
      <div>
        <BlogRouter/>
      </div>
    )
  }
}
export default App;

巢狀路由

巢狀路由兩種寫法:
1.在父元件中直接寫:

import { Route} from 'react-router-dom'
import Right from './Right'
import Role from './Role'
export default class Manage extends Component {
    render() {
        return (
            <div>
                <ul>
                    <li>許可權列表</li>
                    <li>角色列表</li>
                    <Route path='/right-manage/right' component={Right} />
                    <Route path='/right-manage/roles' component={Role} />
                </ul>
            </div>
        )
    }
}
  1. 在路由中寫:
import Manage from '../views/rightmanage/Manage'
import Right from '../views/rightmanage/Right'
import Role from '../views/rightmanage/Role'
 <Route path='/right-manage' render={()=>
            (<Manage>
                    <Switch>
                <Route path='/right-manage/rights' component={Right} />
                <Route path='/right-manage/roles' component={Role} />
                <Redirect from='/right-manage' to='/right-manage/roles' />
                    </Switch>
            </Manage>)
    }/>

** 在父元件中留坑:**

{this.props.children}

路由重定向

** 一定要包 Switch ** 一旦匹配上就不會再繼續匹配 會直接跳出

import {
    Route,
    Redirect,//重定向
    Switch//匹配到第一個符合條件路徑的元件,就停止了
} from 'react-router-dom'
export default class DashBorad extends Component {
    render() {
        return (
            <div>
                <div>頂部導航欄</div>
                <Switch>                  
                    {/* 重定向 */}
                    <Redirect from='/' to='/home' exact />
                    <Route path='*' component={Notfind} />
                </Switch>
            </div>
        )
    }
}

跳轉頁面

路由渲染跳轉:

<Route path='/artic-manege/preview/:myid' component={Prebiew} />

程式設計式跳轉頁面:

this.props.history.push(`/artic-manege/preview/${id}`)

獲取到傳遞的值:

this.props.match.params.myid

高階元件(withRouter)

高階元件,獲取低階元件,生成高階元件 可以實現路由包裹跳轉清空
有些元件沒有被 router 包圍,會獲取不到會有 this.props ,可以用高階元件進行包裹,然後獲取其this.props

import {withRouter} from 'react-router' //路由
//獲取到 this.props 跳轉至 home
this.props.history.push('/home')
export default withRouter(SideMenu)

獲取不到 this.props 解決方案

除了用高階元件的方法,還可以用父元件傳遞this.props給子元件
父元件:

annahistory={this.props.history}

子元件:

this.props.kerwinhitory.push(obj.key)

Redux

Redux 主要用作應用狀態的管理,即 Redux 用一個單獨的常量狀態樹(物件)保持這一整個應用的狀態,這個物件不能直接被改變。如果一些資料變化了,一個新的物件就會被建立(使用 action 和 reducers )
Redux 的工作流程:


圖片來源

同步寫法

store.js 檔案:


import {createStore} from 'redux'//createStore 方法建立一個store回想
// 建立一個reducer ,
//'修改狀態'(接收老狀態,修改的值,深複製之後,再返回一個新的狀態)
const reducer=(prevState={
    // 設定一個初始值
    iscollapsed:false
},action)=>{
    console.log(action)
    // 深複製一份新的把action裡面獲取的值 返回出去
    var newstate={...prevState}
    newstate.iscollapsed=payload  
    return newstate
}//只要狀態已返回,會自動更新
const store=createStore(reducer)
export default store

釋出者(釋出自己的狀態)

store.dispatch({
            type:'mysideMenuCollapsed',
            payload: iscollapsed
        });//store 在action裡面可以獲取到釋出者的值

訂閱者(做出改變者,要獲取新的狀態)

componentDidMount() {
        // 訂閱  注意一定要取消訂閱
       this.unscribe= store.subscribe(()=>{
           //store.getState() 這個可以獲取到新的狀態
            console.log('有人通知我更新了',store.getState())
            this.setState({
                collapsed: store.getState().iscollapsed
            })
        })
    }
     componentWillUnmount(){
    // 取消訂閱
    this.unscribe()
   }

非同步寫法

非同步 action 中介軟體

1、redux-Thunk

redux-thunk可以在actionCreator中返回一個函式,將函式執行,並傳入dispatch和getState兩個引數給這個函式,我們可以在任意時候dispatch
store.js:

import { createStore ,applyMiddleware} from 'redux'//createStore 方法建立一個store回想
import reduxThunk from 'redux-thunk'
// 建立一個reducer ,
//'修改狀態'(接收老狀態,修改的值,深複製之後,再返回一個新的狀態)
const reducer = (prevState = {
    // 設定一個初始值
    roleList:[]//角色側邊導航資料
}, action) => {
    // 深複製
    let { type, payload } = action
    switch (type) {
        case 'setRoleList':
            var newstate = { ...prevState }
            newstate.roleList= payload  
            return newstate
            default :
            return prevState
    }
}//只要狀態已返回,會自動更新
// 預設 action 只能是普通物件{type:''}
// 建立store 順便應用中介軟體thunk 如果action是函式,我來處理
const store = createStore(reducer,applyMiddleware(reduxThunk))
export default store

訂閱者和釋出者案例:
role.js:

actionCreater = () => {
        // middleware  解決非同步處理redux-thunk redux-promise
        return (dispatch) => {
            axios.get("http://localhost:8000/roles").then(res => {
                console.log(res.data)
                // 自己決定什麼時候傳送
                dispatch({
                    type: 'setRoleList',
                    payload: res.data
                })
            })
        }
    }
    componentDidMount() {
        if (store.getState().roleList.length == 0) {
            //發ajax
            store.dispatch(this.actionCreater())
        } else {
            console.log('使用快取', store.getState().roleList)
            this.setState({
                datalist: store.getState().roleList
            })
        }
        //資料改變了 訂閱獲取到新的資料
        this.unscribe = store.subscribe(() => {
            console.log("請求資料結束", store.getState().roleList)
            this.setState({
                datalist: store.getState().roleList
            })
        })
    }
    componentWillUnmount() {
        // 取消訂閱
        this.unscribe()
    }

2、redux-promise

redux-promise可以在actionCreator中返回一個promise物件,他會等待成功後將成功後的結果派發出去
store.js

import reduxPromise from 'redux-promise'
import { createStore, applyMiddleware } from 'redux'//createStore 方法建立一個store回想
const reducer = (prevState = {
    // 設定一個初始值
    rightList: [],//角色側邊導航資料
}, action) => {
    console.log(action)
    // 深複製
    let { type, payload } = action
    switch (type) {
        case 'setRightsList':
            var newstate = { ...prevState }
            newstate.rightList = payload  
            return newstate
        default:
            return prevState
    }
}//只要狀態已返回,會自動更新
const store = createStore(reducer, applyMiddleware(reduxPromise))
export default store

訂閱者和釋出者案例:
role.js:

actionCreater = () => {
        // 返回一個promise物件
        return axios.get("http://localhost:8000/rights").then(res => {
            return {
                type: 'setRightsList',
                payload: res.data
            }
        })
    }
    componentDidMount() {
        if (store.getState().rightList.length == 0) {
            //發ajax
            store.dispatch(this.actionCreater()).then(data=>{
                this.setState({
                    datalist: store.getState().rightList
                })
            })
        } else {
            console.log('使用快取', store.getState().rightList)
            this.setState({
                datalist: store.getState().rightList
            })
        }
    }

核心API-Reducer

Reducer保證是純函式
純函式
1.對外界沒有副作用的函式
2.同樣的輸入,得到同樣的輸出

var myname='kerwin'
function test(myname){
myname='xiaoming'
}
test(myname)

非純函式:

var myname='kerwin'
function test(){
myname='xiaoming'
}
test()

拆分
可以將 Reduer 按照業務模組去拆分
store.js

import { createStore, applyMiddleware ,combineReducers} from 'redux'//createStore 方法建立一個store回想
import reduxThunk from 'redux-thunk'
import reduxPromise from 'redux-promise'
import collapseReducer from './reducers/collapseReducer'
import rightListReducer from './reducers/rightListReducer'
import roleListReducer from './reducers/roleListReducer'
const reducer = combineReducers({
    iscollapsed: collapseReducer,
    roleList: roleListReducer,
    rightList: rightListReducer
})
const store = createStore(reducer, applyMiddleware(reduxThunk, reduxPromise))
export default store
collapseReducer
const collapseReducer = (prevState = false, action) => {
    let { type, payload } = action
    switch (type) {
        case 'sideMenuShow':
            return payload
        default:
            return prevState
    }
}
export default collapseReducer

roleListReducer.js

const roleListReducer = (prevState =[], action) => {
    let { type, payload } = action
    switch (type) {
        case 'setRoleList':
            var newstate = { ...prevState }
            newstate = payload
            return newstate
        default:
            return prevState
    }
}//只要狀態已返回,會自動更新
export default roleListReducer

React-Redux

同步寫法
app.js


import { Provider } from  'react-redux'
import store  from './redux/store
<Provider  store={store}>
        <BlogRouter/>
      </Provider >

釋出者
把方法對映成屬性用
第一個引數是商量好的那個屬性傳給孩子
第二個引數把方法對映成屬性用

import { connect } from 'react-redux'
const mapStateToprops=()=>{
return {
}
} //state 對映成屬性用
const mapDispathToProps={
  actionCreator:(iscollapsed)=>{
        return {
            type: 'sideMenuShow',
            payload: iscollapsed
        }
    }
} 
export default withRouter(connect(mapStateToprops,mapDispathToProps)(TopHeader))

訂閱者接收

import { connect } from 'react-redux'
const mapStateTopprops=(state)=>{
    return {
        iscollapsed:state.iscollapsed
    }//約定isCollapsed 屬性
}
export default withRouter(connect(mapStateTopprops)(SideMenu))

非同步

if (this.props.datalist.length == 0) {
     //直接呼叫改方法 會把狀態傳遞給redux
           this.props.setList()
        }  
//訂閱者 state 中可以獲取到redux中的狀態
const mapStateToprops = (state) => {
    return {
        datalist:state.rightList
    }
} //state 對映成屬性用
//會自動傳遞給redux
const mapDispathToProps = {
    setList : () => {
        // 返回一個promise物件
        return axios.get("http://localhost:8000/rights").then(res => {
            // 自己決定什麼時候傳送
            return {
                type: 'setRightsList',
                payload: res.data
            }
        })
    }
  }
// } //把方法對映成屬性用
export default connect(mapStateToprops,mapDispathToProps)(Right)

Redux和React-Redux關係

mobx

Mobx是一個功能強大,上手非常容易的狀態管理工具。

1、box方法

只能觀察 簡單資料型別

store.js
import { observable } from 'mobx'
const store = observable.box(true)  
export default store
// 傳播者
import store from '../../mobx/store'
 store.set(false)
 // 接收者
 import store from '../../mobx/store'
import {  autorun } from 'mobx'
  autorun(() => {
            console.log(store.get())
        })

2、map方法

觀察複雜資料型別

const store = observable.map({
isshow:true,
list:[],
roleList:[],
rightList:[]
})
 store.set('isshow',false)

mobx 優點:

  1. mobox寫法上更偏向於oop
  2. mobox 對一份資料直接進行修改操作,不需要始終返回一個新的資料
  3. mobox 並非單一的 store。可以多 store
  4. redux 預設以 javaScript 原生物件形式儲存資料,而 mobx 可以用來觀察物件

mobx 缺點
mobx提供的約定及模板程式碼很少,程式碼編寫很自由,如果不做一些約定,比較容易導致團隊程式碼風格不統一
相關中介軟體很少,邏輯層業務整合式問題
遇到的bug
第一個bug:

解決方案:
取消觀察

this.cancel = autorun(() => {
            this.setState({
                code: store.get('isshow')
            }
 //取消觀察
        componentWillUnmount() {
        this.cancel()//取消觀察
    }

第二個bug:

解決方案:
網速很慢的時候資料沒有回來 ajax請求的資料沒有回來

componentWillUnmount() {
        this.setState=()=>{}
        console.log('列表銷燬','取消ajax')
    }

React Hooks

Hook 是 React 16.8 的新增特性。它可以讓你在不編寫 class 的情況下使用 state 以及其他的 React 特性。

useState寫法

import React,{useState}from 'react'
export default function App() {
    const [name, setName] = useState('kerwin')//初始值[狀態,改變狀態的方法]
    const [age, setAge] = useState('12')//初始值[狀態,改變狀態的方法]
    return (
        <div>
            app-{name}-{age}
            <button onClick={()=>{
                setName('xiaoming')
                setAge('18')
            }}>click</button>
        </div>
    )
}

獲取ref

import React, { useState, useRef }from 'react'
const mytext = useRef(null)
<input type='text' onChange={(ev)=>{
                settext(ev.target.value)
            }}  ref={mytext}/>

點選事件

<button onClick={() => handleDeleClick(index)}>dele</button>
 const handleDeleClick=(index)=>{
        console.log(index)
        var newlist=[...list]
        newlist.splice(index,1)
        setlist(newlist)
    }

替代複雜的生命週期(useEffect)

格式: useEffect(處理函式,[依賴])
如果依賴傳的是一個空陣列,相當於 componentWillMount , 掛載前,只執行一次
如果第二個引數不傳,代表任何狀態改變,都會重新執行

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

** 更新: [依賴] 只有依賴改變的時候 才會執行一次**

   // age 更新會重新執行
    useEffect(()=>{
        console.log('建立或更新')
    },[age])

建立/銷燬

   useEffect(() => {
        var id=setInterval(() => {
            console.log(111)
        }, 1000);
        console.log('建立')
        return () => {
            // cleanu
            clearInterval(id)
            console.log('銷燬')
        }
    }, [])

獲取props

export default function Prebiew(props) {}

useCallback 提高執行效率

防止因為元件重新渲染,導致方法被重新建立,提高效能

const test=useCallback(
        () => {
            console.log(text)
        },
        [text]
    )//閉包,快取函式,提高效能
    test()

useReducer和useContext

在 hooks 中提供了的 useReducer 功能,可以增強 ReducerDemo 函式提供類似 Redux 的功能,引入 useReducer 後,useReducer 接受一個 reducer 函式作為引數,reducer 接受兩個引數一個是 state 另一個是 action 。然後返回一個狀態 count 和 dispath,count 是返回狀態中的值,而 dispatch 是一個可以釋出事件來更新 state 的
reducer.js

const reducer = (prevstate, action) => {
    let { type, payload } = action
    switch (type){
        case "Change_text":
            // 深複製
        return {
            ...prevstate, text: payload
        }
         case "Change_list":
            // 深複製
        return {
            ...prevstate, list: payload
        }
    }
    return prevstate
}
export default reducer

index.js (GlobalContext )

index.js (GlobalContext )
import React from 'react'
const GlobalContext = React.createContext()
export default GlobalContext

app.js

import GlobalContext from './store/index'
import reducer from './store/reducer'
import React ,{useReducer,useContext}from 'react'
 const App=() =>{
    // 表示reducer 傳入初始值  代表reducer管理這些狀態
     const [state, dispatch] = useReducer(reducer, {
         isShow: true,
         list: [],
         text: "我是公共的狀態"
     })  //[公共的狀態,改變公共狀態的方法]
    return <GlobalContext.Provider value={{
            state,
            dispatch
        }}> 
            <Child1/>
        </GlobalContext.Provider>
} 
//獲取到app傳來的資訊 state可以直接獲取到 dispatch 可以進行修改狀態
const Child1=()=>{
    let { state, dispatch } = useContext(GlobalContext) //不需要consumer
    // console.log(useContext(GlobalContext))
    return <div>
    //同步:
        child1-{state.text}<button   onClick={()=>{
            dispatch({type:'Change_text',
            payload:'child1111111'
        })
        }}>click</button>
    </div>
   // 異端:
        axios.get("http://localhost:8000/users").then(res=>{
            console.log(res.data)
                dispatch({
                    type: 'Change_list',
                    payload: res.data
                })
            })
}

自定義Hooks

** 當我們想在兩個函式之間共享邏輯時,可以把它提取到第三個函式中
必須以'use'開頭嗎?**
必須如此。這個約定非常重要,不遵循的話,由於無法判斷某個函式是否包含其內部 Hook 的呼叫,React 將無法自動檢測你的Hooks 是否違反了Hook的規則

//為preview 元件提供資料
const usePrebiewDate = (props)=>{
    const [title, settitle] = useState('')
    const [content, setcontent] = useState('')
    const [category, setcategory] = useState([])
    useEffect(() => {
        axios.get(`http://localhost:8000/articles/${props.match.params.myid}`).then(res => {
            console.log(res.data)
            let { title, category, content } = res.data
            settitle(title);
            setcontent(content);
            setcategory(category);
        })
        return () => {
        }
    }, [props])
    return {
        title,
        content,
        category
    }
}
   let { title, content, category}= usePrebiewDate(props)

實戰

文章管理的後臺管理系統——地址

參考文章

React 中setState更新state何時同步何時非同步?
全面掌握 React — useReducer