萬字總結記錄個人React學習路程(附各個代碼參考)

說在最前面

本文是本身最近再學習React的總結和記錄。盡力把每一個示例代碼都寫了出來,盡力寫的通俗易懂一些,也算是鍛鍊本身的講述能力,聽說給別人講的話學的會更快,因此寫下此文。html

若有錯誤或者其餘,還望各位批評指正react

還沒整理徹底,趁着沒開學,我會抓緊時間學習和更新es6

5.9更新 Saga基本使用和案例

傳送門 好像沒用,本地均可以.........npm

更新部分函數組件的用法,提供基本思路和例子編程

JSX

簡介

在JS裏面寫的一些標籤稱爲JSX語法redux

基本語法

在返回的時候不須要加引號api

import React from 'react';
import ReactDOM from 'react-dom';
import logo from './logo.svg';

// 基本
const jsx = <h1>hello react</h1>

// 變量
const name = '123456'
const jsxName = <h1>{ name }</h1>

      // 函數
const user = { firstName: 'tom', lastName: 'jerry' };

function formatName(user) {
  return user.firstName + ' ' + user.lastName;
}

const jsxFunction = <h2>{ formatName(user) }</h2>

      // 對象
const greet = <p>hello, Jerry</p>
const jsxObject = <h2>{ greet }</h2>

      
     // 三元表達式
const showTitle = true;
const title = showTitle ? <h2>{ name }</h2> : null;
const jsxOthers = (<div>{
  /* 條件語句句 */
} { title }
</div>);

// 循環
const arr = [1, 2, 3].map(num => <li key={ num }>{ num } </li>)
const jsxArray = (<div>    {/* 數組 */ }
  <ul>{ arr }</ul>
</div>)


// 注意
const jsxAtt = (<div>
  {
    /* 屬性:靜態值⽤用雙引號,動態值⽤用花括號;class、for等 要特殊處理理。
    * 像class須要寫成className,for須要寫成htmlFor,而且屬性名須要採用駝峯命名法
    * */
  }
<img src={ logo } style={ { width: 100 } } className="img"/></div>)

// 函數傳參
function greeting(name) {
  if (name) {
    return <h1>Hello, {name}!</h1>;
  }
  return <h1>Hello, Stranger.</h1>;
}
let name2 = '測試';
const element = greeting(name2);

// 循環
let names = ['張三','李四','王五'];
let elements = [];
for(let i=0;i<names.length;i++){
  elements.push(<li>{names[i]}</li>);
}
export { jsx, jsxName, jsxFunction, jsxObject, jsxOthers, jsxArray,jsxAtt,element,elements }
複製代碼

組件

​ 能夠將UI切分紅一些獨立的、可複用的部件,數組

class 組件

​ class組件一般擁有狀態生命週期繼承於Component,實現 render方法瀏覽器

基本

import React, { Component } from 'react';
class Home extends Component {
  render() {
    return <h1>Hello, Class Components</h1>;
  }
}
複製代碼

類組件中的狀態管理(秒錶實例)

import React, { Component } from 'react';
class State extends Component {
    
  // 使用state屬性維護狀態,在構造函數中初始化狀態
  constructor(props) {
    super(props);
    this.state = { date: new Date(), number: 0 };
    // this.changeNumber = this.changeNumber.bind(this)
  }

  // 組件掛載時啓動定時器每秒更新狀態
  componentDidMount() {
    this.timerID = setInterval(() => {
      // 使⽤用setState方法更新狀態
      this.setState({ date: new Date() });
    }, 1000);
  }

  // 組件卸載時中止定時器
  componentWillUnmount() {
    clearInterval(this.timerID);
  }

  // 這裏不綁定this 會報錯 兩種方法 第一種 用箭頭函數。第二種在constructor進行綁定聲明
    // setState是異步執行的。執行這個結果是 log 是2不是3 證實是異步執行
  changeNumber = () => {
    this.setState({ number: this.state.number + 1 } )
    this.setState({ number: this.state.number + 2 } )
    // 這裏不能進行直接修改
    console.log(this.state.number)
  }
  // 同步改變
  changeNumber2 = ()=> {
    this.setState((nextState)=>{ return { number: nextState.number + 1 } } )
    this.setState((nextState)=>{ return { number: nextState.number + 2 } } )
    console.log(this.state.number)

    // 使用定時器
    // setTimeout(() => { this.setState((nextState)=>{ return { number: nextState.number + 1 } } ); console.log(this.state.counter); }, 0);

    // 原生事件中修改狀態
    // componentDidMount(){ document.body.addEventListener('click', this.setState((nextState)=>{ return { number: nextState.number + 1 } } ), false) }
  }
  render() {
    return (
        <div> { this.state.date.toLocaleTimeString() } <p>{ this.state.number }</p> <p>setState是異步執行的</p> <button onClick={this.changeNumber}>異步改變number</button> <button onClick={this.changeNumber2}>同步改變number</button> </div>
    )
  }
}
複製代碼

狀態的設置和改變

constructor設置狀態。使用setState來改變狀態。示例如上緩存

關於setState

注意,setState是 異步的!!!!

如何證實?

見示例代碼中changeNumber的描述和實現

如何實現同步?

常見的有三種方法:

  1. 利用函數傳遞參數
  2. 使用定時器
  3. 直接找到DOM元素直接修改

上述三種方法在示例代碼中均可以找到。

函數的this 綁定

通常有兩種方法

  1. constructor裏面用bind方法綁定。例如this.changeNumber = this.changeNumber.bind(this)
  2. 利用ES6的箭頭函數。示例changeNumber = () => { // 執行代碼 }。(**緣由解釋:**箭頭函數默認指向定義它時,所處上下文的對象的this指向)

function組件

基本

import React from "react"; 
import User from "./pages/User";
function App() {  
    return (    
        <div> <User /> </div>  
    ); 
}
export default App;
複製代碼

後文的 react HOOKs會作詳細解釋

組件通信

props通信

適用於父子組件通信

// index.js 
ReactDOM.render(<App title="測試代碼" />, document.querySelector('#root')); // App.js <h2>{this.props.title}</h2> // 輸出爲 測試代碼 複製代碼

Props 的只讀性

官網原話 :組件不管是使用函數聲明仍是經過 class 聲明,都決不能修改自身的 props

狀態提高

官方解釋: 一般,多個組件須要反映相同的變化數據,這時咱們建議將共享狀態提高到最近的共同父組件中去。

實例代碼

import React from 'react'
class Child_1 extends React.Component{
  constructor(props){
    super(props)
  }
  render(){
    return (
        <div> <h1>{this.props.value+'child 1'}</h1> </div>
    )
  }
}
class Child_2 extends React.Component{
  constructor(props){
    super(props)
  }
  render(){
    return (
        <div> <h1>{this.props.value+'child 2'}</h1> </div>
    )
  }
}
class Parent extends React.Component {
  constructor(props){
    super(props)
    this.state = {
      txt:"我是父組件"
    }
    this.handleChange = this.handleChange.bind(this)
  }
  handleChange(e){
    this.setState({
      txt:e.target.value
    })
  }
  render(){
    return (
        <div>
          <input type="text" value={this.state.txt} onChange={this.handleChange}/>
          <p>{this.state.txt}</p>
          <Child_1 value={this.state.txt}/>
          <Child_2 value={this.state.txt}/>
        </div>
    )
  }
}
export default Parent

複製代碼

context 跨組件通信

跨層級組件之間通訊

單層傳遞

// ContextFather.js
import React from 'react';
import ContextChild from './child';
const Context = React.createContext()
const Provider = Context.Provider
const Consumer = Context.Consumer
const data = {
  name: '我是father數據',
}

function ContextApp() {
  return (
      <div> <Provider value={ data }> <Consumer> { value => <ContextChild {...value}/> } </Consumer> </Provider> </div> ) } export default ContextApp // child.js import React, { Component } from 'react'; class ContextChild extends Component { constructor(props) { super(props); } render() { console.log(this.props) return (<h1>hello {this.props.name}</h1>); } } export default ContextChild 複製代碼

這樣能夠獲取到傳遞過來的name

多層傳遞

// Context.js
import React from 'react';

const Context = React.createContext()
const Provider = Context.Provider
const Consumer = Context.Consumer

export {Provider,Consumer}
// ContextFather
import React from 'react';
import ContextChild from './child';
import {Consumer,Provider} from './Context';

const data = {
  name: '我是father數據',
}

function ContextApp() {
  return (
      <div> <Provider value={ data }> // 這個層級不須要就不用寫 Consumer <ContextChild/> </Provider> </div>
  )
}
export default ContextApp

// child.js
import React, { Component } from 'react';
import ContextChild2 from './child2';
import {Consumer} from './Context';

class ContextChild extends Component {
  constructor(props) {
    super(props);
  }
  render() {
    return (
        <div> <h1>我是第一層</h1> // 這裏獲取的是一個空值,沒有數據 <h2>{this.props.name}</h2> // 這個層級須要就吧數據傳遞過去 <Consumer> { value => <ContextChild2 {...value}/> } </Consumer> </div> ) } } export default ContextChild // child2.js import React, { Component } from 'react'; class ContextChild2 extends Component { constructor(props) { super(props); } render() { console.log(this.props) console.log('我是第二層的') return ( <div> <h1>我是第二層的</h1> <h1>{this.props.name}</h1> </div> ); } } export default ContextChild2 複製代碼

特別注意

  1. 不能有多個提供者,例如在每一個文件裏 寫上Context.js裏面的語句。則會獲取不到數據。不一樣的provider提供的值不一致
  2. 在React的官方文檔中,Context被歸類爲高級部分(Advanced),屬於React的高級API,但官方並不建議在穩定版的App中使用Context。不過,這並不是意味着咱們不須要關注Context。事實上,不少優秀 的React組件都經過Context來完成⾃本身的功能,好比react-redux

redux 相似Vuex

沒學過Vue的能夠理解爲全局狀態管理,即你在哪裏都能訪問的到(我我的是這麼理解的)

知識擴展

Reducer

解釋: reducer 就是一個純函數,接收舊的 stateaction,返回新的 state

reduce

本例子來源於MDN

const array1 = [1, 2, 3, 4];
const reducer = (total, currentValue) => total + currentValue;

// 1 + 2 + 3 + 4
console.log(array1.reduce(reducer));
// expected output: 10

// 5 + 1 + 2 + 3 + 4
console.log(array1.reduce(reducer, 5));
// expected output: 15
複製代碼
compose(沒太理解)

compose就是執行一系列的任務(函數)

思考

如何輸出1 2 3

function f1() {  console.log("1"); } 
function f2() {  console.log("2"); }
function f3() {  console.log("3"); }
複製代碼

第一種

f1();f2();f3();

第二種

f3(f2(f1()))

第三種

function compose(...funcs) {
  // funcs 是一個函數列表
  if (funcs.length === 0) {
    console.log('empty');
  } else if (funcs.length === 1) {
    return funcs[0];
  } else {
    return funcs.reduce(
        (left, right) => (...args) => {
          // console.log(args)
          // console.log(right)
          // console.log(left)
          right(left(...args)) // 至關於 f3(f2(f1()))
        }
    );
  }
}
compose(f1,f2,f3)()
複製代碼

Redux

使用步驟
  1. 須要一個store來存儲數據
  2. storereducer初始化state並定義state修改規則
  3. 經過dispatch一個action來提交對數據的修改
  4. action提交到reducer函數里,根據傳入的actiontype,返回新的 state

實例代碼

// 建立store store/ReduxStore
import { createStore } from 'redux';
// 建立數據並指定行爲
const counterReducer = (state = 0, action) => {
  switch (action.type) {
    case 'add':
      return state + 1
    case 'minus':
      return state - 1
    default:
      return state
  }
}
// 建立一個store
const store = createStore(counterReducer)
export default store
複製代碼
// ReduxPage
import React, { Component } from 'react';

import store from '../store/ReduxStore';

export default class ReduxPage extends Component {
  componentDidMount() { // 掛在以後
    store.subscribe(() => { // 執行訂閱
      console.log('subscribe');
      this.forceUpdate(); // 從新render
      //this.setState({}); // 這個也能夠,內部方法也是forceUpdate
    });
  }
// 執行行爲
  add = () => {
      // 派發action
    store.dispatch({ type: 'add' });
  };
  minus = () => {
    store.dispatch({ type: 'minus' });
  };
  stayStatic = () => {
    store.dispatch({ type: 'others' });
  };

  render() {
    console.log('store', store);
    return (
        <div> <h1>ReduxPage</h1> <p>獲取store數據</p> <p>{ store.getState() }</p> <button onClick={ this.add }>+</button> <button onClick={ this.minus }>-</button> <button onClick={ this.stayStatic }>+-</button> </div>
    );
  }
}
複製代碼
注意

若是點擊後 數據沒有更新。則是訂閱狀態沒有改變(也就是沒有render

着重注意點
  1. createStore 建立store
  2. reducer初始化、修改狀態函數
  3. getState 獲取狀態值
  4. dispatch提交更新
  5. subscribe 變更訂閱

react-redux

React的方式寫來Redux

提供了兩個api

  1. Provider 爲後代組件提供store(相似Context)
  2. connect 爲組件提供數據和變動⽅方法
react-thunk

原來的只能夠同步執行,安裝以後,能夠進行異步操做

react-logger

只能夠再開發環境下使用。用來輸出各個action

store

你們會發現其實仍是有 redux的引入,說明react-redux也是基於Redux

import { createStore, applyMiddleware,combineReducers } from 'redux';
import logger from 'import React, { Component } from 'react';
import { connect } from 'react-redux';

class ReactReduxPage extends Component {
  render() {
    const { num, add, minus,asyAdd } = this.props;
    // console.log(this.props) // 本來這裏面什麼都沒有,再進行connect映射後出現了
    return (
        <div>
          <h1>ReactReduxPage</h1>
          <p>{ num }</p>
          <button onClick={ add }>+</button>
          <button onClick={ minus }>-</button>
          <button onClick={ asyAdd }>asyAdd</button>
        </div>
    );
  }
}

const mapStateToProps = state => {
  console.log(state)
  return { num: state };
  // return { num: state.counter };// 加了combineReducers 以後須要這麼作 由於是一個對象
};

const mapDispatchToProps = {
  add: () => {
    return { type: 'add' };
  },
  minus: () => {
    return { type: 'minus' };
  },
  asyAdd: ()=> dispatch =>{
    setTimeout(()=>{
      dispatch ({type: 'add'})
    },1000)
  }
};
export default connect(
    mapStateToProps,//狀態映射 mapStateToProps
    mapDispatchToProps,//派發事件映射
)(ReactReduxPage)
';
import thunk from 'redux-thunk';


const counterReducer = (state = 0, action) => {
  switch (action.type) {
    case 'add':
      return state + 1
    case 'minus':
      return state - 1
    default:
      return state
  }
}
// const store = createStore(counterReducer)
// const store = createStore(combineReducers({counter: counterReducer}), applyMiddleware(logger, thunk))
const store = createStore(counterReducer, applyMiddleware(logger, thunk))
export default store
複製代碼

applyMiddleware提供擴展中間件(有順序,這個事先執行logger,在執行thunk

combineReducers 命名空間,當你有多個store的時候,使用這個,就能夠用對象的方式訪問各個store

ReactReduxPage
import React, { Component } from 'react';
import { connect } from 'react-redux';

class ReactReduxPage extends Component {
  render() {
    const { num, add, minus,asyAdd } = this.props;
    // console.log(this.props) // 本來這裏面什麼都沒有,再進行connect映射後出現了
    return (
        <div> <h1>ReactReduxPage</h1> <p>{ num }</p> <button onClick={ add }>+</button> <button onClick={ minus }>-</button> <button onClick={ asyAdd }>asyAdd</button> </div>
    );
  }
}

const mapStateToProps = state => {
  console.log(state)
  return { num: state };
  // return { num: state.counter };// 加了combineReducers 以後須要這麼作 由於是一個對象
};

const mapDispatchToProps = {
  add: () => {
    return { type: 'add' };
  },
  minus: () => {
    return { type: 'minus' };
  },
  asyAdd: ()=> dispatch =>{
    setTimeout(()=>{
      dispatch ({type: 'add'})
    },1000)
  }
};
export default connect(
    mapStateToProps,//狀態映射 mapStateToProps
    mapDispatchToProps,//派發事件映射
)(ReactReduxPage)
複製代碼

固然,若是嫌棄太亂,能夠單獨抽離出來放。最後導入進來就能夠

組件複合

這個也是官方推薦的作法: 咱們推薦使用組合而非繼承來實現組件間的代碼重用。

學過Vue的同窗,我我的理解爲相似於Vue的插槽

// Layout.js
import React, { Component } from 'react';
import TabBar from '../components/TabBar';
class Layout extends Component{
  constructor(props) {
    super(props);
  }
  componentDidMount() {
    const {title = '首頁'} = this.props
    document.title = title
  }
    
  // 兼容具名和不具名 compatible
  compatibleNamed(children) {
    let doc = []
    if (children.$$typeof) {
      doc.push(children)
    } else {
      for (let item in children){
        doc.push(children[item])
      }
    }
    return doc
  }
  render() {
    console.log(this.props)
    return (
        <div> <h1>我是佈局頁面</h1> {/* 不具名的使用*/} {/*{*/} {/* this.props.children*/} {/*}*/} {/* 這個是具名的使用*/} { this.props.children.btn } <h2>兼容事後</h2> {this.compatibleNamed(this.props.children).map((item,index)=>{ return ( <div key={index}> {item} </div> ) })} { this.props.showTab && <TabBar/> } </div>
    )
  }
}
export default Layout
// Home.js
import React, { Component } from 'react';
import Layout from '../ComponentCombination/Layout';

class Home extends Component {
  render() {
    return (
        <DisNamed/>
    )
  }
}

function Named(props) {
  return (
      <Layout showTab={ false } title='商城首頁'> <h1>我是首頁</h1> </Layout>
  )
}
function DisNamed() {
  return (
      <Layout showTab={ false } title='商城首頁'> { { btn: <button>我是按鈕</button> } } </Layout>
  )
}
export default Home
// TabBar.js
import React, { Component } from 'react';
class TabBar extends Component{
  render() {
    return (
        <div> <h1>我是TabBar</h1> </div>
    )
  }
}
export default TabBar
複製代碼

不具名

上面例子容易混淆,寫一個簡單點的

import React, { Component } from 'react';
import TabBar from '../components/TabBar';
class Layout extends Component{
  constructor(props) {
    super(props);
  }
  render() {
    console.log(this.props)
    return (
        <div> <h1>我是佈局頁面</h1> {/* 不具名的使用*/} { this.props.children } { this.props.showTab && <TabBar/> } </div>
    )
  }
}
export default Layout
複製代碼

this.props.children所展現的是兩個Layout之間的內容

例如

<Layout>
    <h1>hello</h1>
    </Layout>
複製代碼

this.props.children展現的就是<h1>hello</h1>

具名

//Layout
import React, { Component } from 'react';
import TabBar from '../components/TabBar';
class Layout extends Component{
  constructor(props) {
    super(props);
  }
  render() {
    console.log(this.props)
    return (
        <div> <h1>我是佈局頁面</h1> {/* 這個是具名的使用*/} { this.props.children.btn } { this.props.showTab && <TabBar/> } </div>
    )
  }
}
export default Layout


// Home.js
import React, { Component } from 'react';
import Layout from '../ComponentCombination/Layout';

class Home extends Component {
  render() {
    return (
        <Named/>
    )
  }
}
function DisNamed() {
  return (
      <Layout showTab={ false } title='首頁'> { { btn: <button>我是按鈕</button> } } </Layout>
  )
}
export default Home
複製代碼

在傳遞具名組件的時候,須要用雙{{}}包裹。使用的就當屬性使用就能夠了

差別與兼容

具名不具名 的差別在於傳遞過來的時候this.props.children是否含有$$typeof屬性。若是不含有,則是具名。反之,含有則是不具名

如何兼容?

知道了二者差別。就從差別下手

compatibleNamed(children) {
    let doc = []
    if (children.$$typeof) { // 若是不是具名 直接放到數組裏
      doc.push(children)
    } else {
      for (let item in children){ // 若是是具名,則便利children。把裏面的內容放進數組裏
        doc.push(children[item])
      }
    }
    return doc // 返回的是一個數組
  }
// 使用
render(){
    return (
  {this.compatibleNamed(this.props.children).map((item,index)=>{
            return (
                <div key={index}> {item} </div>
            )
          })}
}
複製代碼

高階函數(柯里化)

高階組件是一個工廠函數,它接收一個組件並返回另外一個組件。

簡單例子

function Child() {
  return (
      <div> child </div>
  )
}

// Cmp是組件 props 是組件的props
const foo = Cmp => props =>{
  return (
      <div style={{boder: '1px solid #ccc'}}> <Cmp {...props}/> </div> ) } function APP() { const Foo = foo(Child) return ( <div> <Foo/> </div> ) } 複製代碼

修改之前Context的例子

建立 高階組件

import { Consumer } from './Context';
import React from 'react';
//Cum 是組件 props是組件傳遞的props
const HOC = Cum => props => {
  return (
      <Consumer> { value => <Cum { ...props } { ...value }/> } </Consumer> ) } export default HOC 複製代碼

原來的 Context寫法

import React, { Component } from 'react';
import ContextChild2 from './child2';
import HOC from './Hoc Hooks';
import {Consumer} from './Context';

class ContextChild extends Component {
  constructor(props) {
    super(props);
  }
  render() {
    return (
        <div> <h1>我是第一層</h1> <h2>{this.props.name}</h2> <Consumer> { value => <ContextChild2 {...value}/> } </Consumer> </div> ) } } export default ContextChild 複製代碼

改用HOC(高階組件)以後

// child
import React, { Component } from 'react';
import ContextChild2 from './child2';
import HOC from './Hoc Hooks';
class ContextChild extends Component {
  constructor(props) {
    super(props);
  }
  render() {
    return (
        <div> <h1>我是第一層</h1> <h2>{this.props.name}</h2> {/*直接調用*/} <ContextChild2/> </div>
    )
  }
}
export default HOC(ContextChild)
// child2.js
import React, { Component } from 'react';
import HOC from './Hoc Hooks';
class ContextChild2 extends Component {
  constructor(props) {
    super(props);
  }
  render() {
    console.log(this.props)
    console.log('我是第二層的')
    return (
        <div> <h1>我是第二層的</h1> <h1>{this.props.name}</h1> </div>
    );
  }
}
export default HOC(ContextChild2)
複製代碼

本來須要寫Consumer如今只須要一個函數就解決了。很是方便

詳細解釋

可能仍是會有點迷瞪。我在詳細的說一次

import { Consumer } from './Context';
import React from 'react';

const HOC = Cum => props => {
  return (
      <Consumer> { value => <Cum { ...props } { ...value }/> } </Consumer> ) } export default HOC 複製代碼

Cum是組件,props是組件的所攜帶的參數,valueprovider所分發的數據.

類比一下下面的.就是把組件看成了參數,經過傳入一個組件,返回另外一個新的組件

<Consumer>
            {
              value => <ContextChild {...value}/> } </Consumer>
複製代碼

徹底修改後

// ContextFather.js

import React from 'react';
import ContextChild from './child';
import {Consumer,Provider} from './Context';

const data = {
  name: '我是father數據',
}

function ContextApp() {
  return (
      <div> <Provider value={ data }> {/*<Consumer>*/} {/* {*/} {/* value => <ContextChild {...value}/>*/} {/* }*/} {/*</Consumer>*/} <ContextChild/> </Provider> </div> ) } export default ContextApp // child.js import React, { Component } from 'react'; import ContextChild2 from './child2'; import HOC from './Hoc Hooks'; class ContextChild extends Component { constructor(props) { super(props); } render() { return ( <div> <h1>我是第一層</h1> <h2>{this.props.name}</h2> <ContextChild2/> </div> ) } } export default HOC(ContextChild) // child2.js import React, { Component } from 'react'; import HOC from './Hoc Hooks'; class ContextChild2 extends Component { constructor(props) { super(props); } render() { console.log(this.props) console.log('我是第二層的') return ( <div> <h1>我是第二層的</h1> <h1>{this.props.name}</h1> </div> ); } } export default HOC(ContextChild2) 複製代碼

路由

安裝

​ react-router包含3個庫,react-router、react-router-dom和react-router-native。

​ react-router提供最 基本的路由功能,實際使用的時候咱們不會直接安裝react-router,而是根據應用運行行的環境選擇安裝

​ react-router-dom(在瀏覽器器中使⽤用)

​ react-router-native(在rn中使用)。

​ react-router-dom和 react-router-native都依賴react-router,因此在安裝時,react-router也會⾃自動安裝

npm install --save react-router-dom

基本使用

經常使用 BrowserRouter 、連接-Link、路由-Route、獨佔路由Switch、重 定向路由-Redirect

// RouterPage
import React, { Component } from 'react';
import { BrowserRouter, Link, Route,Switch } from 'react-router-dom';
import HomePage from './HomePage';
import UserPage from './UserPage';
import SearchPage from './Search';
import Login from './Login';
export default class RouterPage extends Component {
  render() {
    return (
        <div>
          <h1>RouterPage</h1>
          <BrowserRouter>
            <nav>
              <Link to="/">首頁  </Link>
              <Link to="/user">用戶中心  </Link>
              
              <Link to="/login">登錄  </Link>
            </nav>
            {/* 根路路由要添加exact,實現精確匹配 不加這個可能會出現屢次渲染 */ }
            <Switch>
              {/*匹配到以後就不在繼續往下匹配 Switch做用*/}
              <Route exact path="/" component={ HomePage }/>
              {/*<Route path="/user" component={ UserPage }/>*/}
              <Route path="/login" component={ Login }/>
              
              {/*404 頁面 必定要放到最後*/}
              <Route component={() => <h1>404</h1>} />
            </Switch>
          </BrowserRouter>
        </div>
    );
  }
}
複製代碼

動態路由

// RouterPage
<Link to="/search/123">詳情  </Link>
<Route path="/search/:id" component={SearchPage} />
// Search
import React from 'react';
function SearchPage(props) {
  console.log(props) // 有三個 match history location
  return(
      <div>
      <p>經過這樣獲取到傳遞的參數</p>
        SearchPage{props.match.params.id}
      </div>
  )
}
export default SearchPage
複製代碼

路由守衛(重點)

​ 案例:好比你須要在登錄以後才能進入到用戶頁面,這時候就須要用路由守衛。

熟悉Vue的同窗應該能知道我想要表達的意思

//RouterPage
...
import RouterGuard from './RoutingGuard';
...
<Link to="/user">用戶中心  </Link>
<Link to="/login">登錄  </Link>
<RouterGuard path ='/user' isLogin={true} Cmp={UserPage}/>
...
複製代碼

RouterGuard(重點)

// RouterGuard
import React from 'react';
import { connect } from 'react-redux';
import { Route, Redirect } from 'react-router-dom';

function RouterGuard(props) {
  const { Cmp, path, isLogin, ...rest } = props
  console.log(rest) // 一些剩下的參數
  return (
      <Route
          { ...rest }
          render={ prop =>{
            console.log(prop) // 一些路由的參數
            return isLogin ? (<Cmp { ...prop } />) :
                (<Redirect to={ { pathname: '/login', redirect:props.location.pathname} }/>
                )} }
      >
      </Route>
  )
}
export default connect(
    state => {
      return {
        isLogin: state.user.isLogin,
      }
    },
)(RouterGuard)
複製代碼

這裏面主要是用了渲染函數 render, 還有一個三元表達式

其餘頁面代碼

// LoginStore 記得用provider 包裹APP
import { createStore, applyMiddleware, combineReducers } from 'redux';
import logger from 'redux-logger';
import thunk from 'redux-thunk';

const initialLogin = { isLogin: false, name: null };
const userInfo = (state = {...initialLogin}, action) => {
  switch (action.type) {
    case 'getUser':
      return { ...state,isLogin: false }
    case 'LoginSuccess':
      return { ...state,isLogin: true,name: '阿琛' }
    case 'LoginFail':
      return { ...initialLogin }
    default:
      return state
  }
}
const store = createStore(
    combineReducers({
          user: userInfo
        }),
    applyMiddleware(logger, thunk))
export default store
// Login
import React from 'react';
import { connect } from 'react-redux';
import {Redirect} from 'react-router-dom'

function Login(props) {
  const {location,login,isLogin} = props
  console.log(props)
  const redirect = location.redirect?location.redirect:'/'
  if (isLogin){
    return  <Redirect to={redirect} />; } return ( <div> <h1>LoginPage</h1> <button onClick={login}>登錄</button> </div> ) } export default connect(state => ({ isLogin: state.user.isLogin, }), { login: () => { return { type: 'LoginSuccess' }; }, }, )(Login) 複製代碼

React全家桶

前面介紹了好多了,接下來介紹一些尚未介紹的

redux-saga

​ Redux-saga是Redux的一箇中間件,主要集中處理react架構中的異步處理工做,被定義爲generator(ES6)的形式,採用監聽的形式進行工做。

做用和react-thunk差很少。都是爲了解決異步

知識基礎

Generato

Generator 函數是 ES6 提供的一種異步編程解決方案,語法行爲與傳統函 數徹底不同,詳細參考參考阮一峯老師講解

安裝

npm install --save redux-saga

案例

也是一個登錄功能

// RouterPage  加上一個saga 登錄
...
<Link to="/login2">Saga登錄  </Link>

<Route path="/login2" component={ SagaLogin }/>
...

//SageLogin
import React,{useState} from 'react';
import { connect } from 'react-redux';
import { Redirect } from 'react-router-dom'

// 這裏用的是Hooks  寫起來順手 直接用這個了
function SagaLogin(props) {
  let [name,setName] = useState('')
  const { location, login, isLogin, loading,error } = props
  console.log(props)
  const redirect = location.redirect ? location.redirect : '/'
  if (isLogin) {
    return <Redirect to={ redirect }/>;
  }
  return (
      <div>
        <h1>LoginPage</h1>
        <p>{name}</p>
        {error && <p>{error}</p>}
        <input type="text" onChange={(e)=>{setName(e.target.value)}}/>
        <button onClick={ () => login(name) }>
          { loading ? '登陸中...' : '登陸' }
        </button>
      </div>
  )
}

// 這裏面的就再也不解釋了  再說react-thunk 解釋過
export default connect(state => (
    {
      isLogin: state.user.isLogin,
      loading: state.user.loading,
      error: state.user.error,
    }), {
    // 這裏調用Saga的login 函數,由於有事件監聽。觸發了loginHandle
      login: name => ({ type: 'login', name }),
    },
)(SagaLogin)
複製代碼

重點

//Saga 這裏是把邏輯分離了出來,只是爲了更好維護
import { call, put, takeEvery } from 'redux-saga/effects';
// 模擬登陸接口
const UserService = {
  login(name) {
    return new Promise((resolve, reject) => {
      setTimeout(() => {
        if (name === '阿琛') {
          resolve({ name: '阿琛' });
        } else {
          reject('用戶名或密碼錯誤');
        }
      }, 1000);
    });
  },
};
// worker saga
function* loginHandle(action) {
  console.log('loginHandle');
  try {
      // 派發清求 到ReduxSage 使得loading爲true
    yield put({ type: 'requestLogin' });
      // 執行回調函數。獲取結果
    const res = yield call(UserService.login, action.name);
      // 把結果派發回去
    yield put({ type: 'requestSuccess', res });
  } catch (err) {
    yield put({ type: 'requestFailure', err });
  }
}
// watcher saga 事件監聽 監聽login事件
function* mySaga() {
  yield takeEvery('login', loginHandle);
}
export default mySaga;


// ReduxSage
import { createStore, combineReducers, applyMiddleware } from 'redux';
import mySaga from './Saga';
import createSagaMiddleware from "redux-saga";

// 建立一箇中間件
const sagaMiddleware = createSagaMiddleware();

// 初始化狀態
const initialLogin = { isLogin: false, loading: false, name: '', error: '' };

// 定義Reducer
// 注意 必須又default 從action 裏面拿到傳遞過來的參數
function loginReducer(state = { ...initialLogin }, action) {
  switch (action.type) {
    case 'requestLogin':
      return { ...initialLogin, loading: true };
    case 'requestSuccess':
      return { ...state, isLogin: true, loading: false,name: action.name };
    case 'requestFailure':
      console.log(action)
      return {...state,error: action.err}
    default:
      return state;
  }
}

const store = createStore(combineReducers(
    { user: loginReducer }
    ),
    // applyMiddleware(thunk)
    applyMiddleware(sagaMiddleware)
);
// 記得運行一下
sagaMiddleware.run(mySaga)
// 若是按看上面的例子了話,記得換一下store 再index.js 裏用provider 來傳遞store
export default store;
複製代碼

如下的尚未學習或者整理,最近幾天會抓緊時間整理出來

Dva

Umi

mobx

生命週期(暫無整理)

React HOOKs

整理不全,正在抓緊

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

解決問題

  • 在組件之間複用狀態邏輯很難,可能要用到render props和高階組件,React 須要爲共享狀態邏輯提供更好的原生途徑,Hook 使你在無需修改組件結構的狀況下複用狀態邏輯
  • 複雜組件變得難以理解,Hook 將組件中相互關聯的部分拆分紅更小的函數(好比設置訂閱或請求數據)
  • 難以理解的 class,包括難以捉摸的this

注意事項

  • 只能在函數最外層調用 Hook。不要在循環、條件判斷或者子函數中調用。
  • 只能在 React 的函數組件中調用 Hook。不要在其餘 JavaScript 函數中調用

useState

  • useState 會返回一對值:當前狀態和它的函數,你能夠在事件處理函數中或其餘一些地方調用這個函數。它相似 class 組件的 this.setState,可是它不會把新的 state 和舊的 state 進行合併
  • useState 惟一的參數就是初始 state

計數器簡單實例

import React,{useState} from 'react';

function Counter() {
    // useState會返回兩個,一個是當前值。一個是改變它的函數
  let [state,setState] = useState({num: 0})
  let [num,setNum] = useState(0)
  // 兩種方式 看我的喜愛
  return (
      <div> <p>{state.num}</p> <p>{num}</p> <button onClick={()=>{setState({num: state.num +1})}}>+</button> <button onClick={()=>{setNum(num + 1)}}>+</button> </div>
  )
}
複製代碼

每次渲染都是獨立的閉包

  • 每一次渲染都有它本身的 Props and State
  • 每一次渲染都有它本身的事件處理函數
  • alert會「捕獲」我點擊按鈕時候的狀態。
// alter 捕得到是點擊時候得狀態
function Counter2 () {
  // useState 返回兩個,一個是當前狀態的屬性,一個是修改狀態的方法。
  let [state, setState] = useState({num: 0})
  let changeLater = ()=>{ // 這個alter 會捕獲當前得狀態。即點擊按鈕時候得狀態
    setTimeout(()=>{
      alert(state.num)
    }, 2000)
  }
  return (
      <div> <p>{ state.num }</p> <button onClick={ () => setState({num: state.num+1}) }>+</button> <button onClick={ changeLater }>changeLater</button> </div>
  )
}
複製代碼

函數式更新(解決上例的方法)

// 改變狀態注意
function Counter3 () {
  // useState 返回兩個,一個是當前狀態的屬性,一個是修改狀態的方法。
  let [state, setState] = useState({num: 0})
  let lazyAdd = ()=>{ // 這個alter 會捕獲 當前得狀態。即點擊按鈕時候得狀態
    setTimeout(()=>{
      setState({num: state.num+1})
    }, 2000)
  }
  let lazyAdd2 = ()=>{ // 這個alter 會捕獲以後的狀態。
    setTimeout(()=>{
      setState((state) => ({num: state.num+1}))
    }, 1000)
  }
  return (
      <div> <p>{ state.num }</p> <button onClick={ () => setState({num: state.num+1}) }>+</button> <button onClick={ lazyAdd }>lazyAdd</button> <p>若是這樣寫得話,會獲取到點擊時候得狀態在進行改變</p> <button onClick={ lazyAdd2 }>lazyAdd2</button> <p>這樣寫,改變了整個state,就能夠正常相加了</p> </div>
  )
}
複製代碼

​ 若是新的 state 須要經過使用先前的 state 計算得出,那麼能夠將函數傳遞給 setState。該函數將接收先前的 state,並返回一個更新後的值

惰性state

  • initialState 參數只會在組件的初始渲染中起做用,後續渲染時會被忽略
  • 若是初始 state 須要經過複雜計算得到,則能夠傳入一個函數,在函數中計算並返回初始的 state,此函數只在初始渲染時被調用
// 惰性state
// initialState 初始狀態參數只會在組件初始渲染得時候調用,後續被忽略
// 且不會本身整合state,須要本身整合
function Counter4 () {
  let [state, setState] = useState(function () {
    console.log(' 初始化')
    return {num: 0, name:'計算器'}
  })
  return (
      <div> <p>{state.num}</p> <button onClick={ () => setState({num: state.num+1}) }>+</button> <p>只會輸出一次log</p> <p>React.StrictMode 模式下會輸出兩次log</p> <p>這是展現他不會整合state</p> <p>{ state.name }:{state.num}</p> <p>若是點擊上面得button,則name會消失,下面這個則不會</p> <button onClick={ () => setState({...state, num: state.num+1}) }>+</button> </div>
  )
}
複製代碼

按第一個button 計算器消失的緣由: setState不會本身整合State,須要咱們本身手動進行整合

性能優化

Object.is

MDN,使用Object.is比較。

function Counter5 () {
  let [state, setState] = useState(function () {
    return {num: 0, name:'計算器'}
  })
  console.log('當state發生改變得時候,會從新得渲染這個函數,則會繼續得輸出')
  console.log('Render5 ')
  return (
      <div> <p>{state.num}</p> <button onClick={ () => setState({...state, num: state.num+1}) }>輸出+</button> <p>點擊這個不會輸出 Render5</p> <button onClick={ () => setState(state) }>不輸出+</button> <p>緣由,內部當發現這個state沒有發生改變得時候,將再也不會渲染這個</p> </div>
  )
}
複製代碼

作緩存 CallBack

緩存函數

let lastClick;
let lastClick2;
function Counter7(callback, deps) {
  let [state, setState] = useState({num: 0})
  const contralAdd = ()=> setState({num: state.num+1})
  console.log(lastClick === contralAdd)
  lastClick = contralAdd  // 一直輸出false 證實不是同一個函數
  /**********************************************************************************/
  const contralAdd2 = useCallback(
      ()=> setState({num: state.num+1}), []
      // 這個數組稱之爲依賴數組,裏面放屬性,屬性發生變化,他就從新渲染
      // 若是不寫 state.num 那點擊下面得按鈕只會變化一次。
      // 若是寫了得話,則會每次改變都會從新渲染
  )
  console.log(lastClick2 === contralAdd2)
  lastClick2 = contralAdd2  // 一直輸出true 證實是同一個函數
  return (
      <div> <p>{state.num}</p> <button onClick={ contralAdd }>+</button> <p>這樣得話每次會生成一個新得函數,性能會變低</p> <button onClick={ contralAdd2 }>+</button> <p>這樣得話更具log顯示,得出是一個函數,提升性能</p> </div>
  )
}

複製代碼
三種結果
  1. 若是點擊上面的 加號 log輸出
lastClick === contralAdd false
lastClick2 === contralAdd2 true
複製代碼

緣由:state.num 發生了改變,從新渲染了 Counter。因此第一個輸出false。 第二個由於加了useCallback,依賴數組裏面沒有添加屬性,致使,因此程序認爲他沒有發生改變,因此輸出 true

  1. 若是點擊下面的 + 號 log輸出

    lastClick === contralAdd false
    lastClick2 === contralAdd2 true
    複製代碼

    它一樣會輸出這個,緣由,若是不寫依賴屬性,那點擊下面得按鈕只會變化一次。而數值變化了。因此第一個爲true 第二個爲false,而且再點擊這個按鈕不會再發生變化

    (說的有點不許確,準確來講是發生變化了,由於閉包的緣由,致使第二個函數的state.num一直指向初始化的0 因此纔沒有改變)

  2. 添加依賴屬性以後。輸出結果

    lastClick === contralAdd false
    lastClick2 === contralAdd2 false
    複製代碼

    這個不用多說了,依賴屬性發生了改變。致使兩個函數都從新渲染

memo

純函數,作優化,或者叫緩存,緩存組件

import React,{memo,useMemo,useState} from 'react';

function Child() {
  console.log('我是Child')
  return (
      <h2>我是child</h2>
  )
}
function Child2(props) {
  console.log('render child2')
  console.log(props)
  return (
      <p>我是Child2{props.name}</p>
  )
}
Child = memo(Child)
Child2 = memo(Child2)
function MemoApp() {
  let [state,setState] = useState({num: 0})
  let [name, setName] = useState('測試')
  return(
      <div>
        <p>{state.num}</p>
        <button onClick={()=>{setState({num: state.num +1 })}}>+</button>
        <br/>
        <input type="text" value={name} onChange={(e)=>{setName(e.target.value)}}/>
        <Child/>
        <Child2 name={name}/>
      </div>
  )
}
export { MemoApp }
複製代碼

​ 若是不加上memo,則只要點擊 + 號,Child就會發生渲染。

通俗解釋:只要上次渲染結果和此次相同。就不會渲染,不然渲染

useMemo

也是作緩存用的。緩存數據

let lastData
function UseMemoApp() {
  let [state, setState] = useState(0)
  let [name, setName] = useState('測試')
  const data = state
  const data2 = useMemo(() => (state), []);
  console.log('data===oldData ', data === lastData);
  const addClick = useCallback(() => setState(state + 1), [state])
  lastData = data;
  return(
      <div> <p>{state}</p> <p>{data2.state}</p> <button onClick={addClick}>+</button> <p>改變input的值,輸出true 證實數據作了緩存</p> <input type="text" value={name} onChange={(e)=>{setName(e.target.value)}}/> </div> ) } 複製代碼

上面那個userCallback能夠無視,我就是作了一個緩存。重點關注兩個data

改變了input內的值以後,log 輸出true,證實數據緩存成功,數據發生改變,輸出false

useReducer

  • useState 的替代方案。它接收一個形如 (state, action) => newState 的 reducer,並返回當前的 state 以及與其配套的 dispatch 方法
  • 在某些場景下,useReducer 會比 useState 更適用,例如 state 邏輯較複雜且包含多個子值,或者下一個 state 依賴於以前的 state 等

例子,計數器

import React, { useReducer } from 'react';
// 初始化狀態
const initialState = 0;

// 初始一個reducer state 狀態 action 派發狀態
function reducer(state, action) {
  switch (action.type) {
    case 'increment':
      return { number: state.num + 1 };
    case 'decrement':
      return { number: state.num - 1 };
    default:
      throw new Error();
  }
}

// 把數據變成 對象
function init(initialState) {
  return { num: initialState }
}

function ReducerCounter() {
  const [state, dispatch] = useReducer(reducer, initialState, init);
  return (
      <> Count: { state.num } <p></p> <button onClick={ () => dispatch({ type: 'increment' }) }>+</button> <button onClick={ () => dispatch({ type: 'decrement' }) }>-</button> </> ) } export { ReducerCounter } 複製代碼

利用useReducer 寫一個useState

自定義hooks

若是函數的名字以 use 開頭而且調用了其餘的 Hook,則就稱其爲一個自定義 Hook。

這裏只作一個簡單解釋。後面會詳細介紹

function useStateSelf(initState) {
  function init(initialState){
    return {number:initialState};
  }
  // userCallback 作緩存
  const reducer = useCallback((state,action) => {
    console.log(action)// 看log 輸出判斷 返回值
    console.log(state)
    return action.payload  // 加{} 須要 寫 action.payload
  	// return payload// 不加{}直接寫action
  })
  let [state,dispatch] = useReducer(reducer, initState,init)
  console.log(dispatch)
  function setState(payload) {
    console.log(payload)  // 整個對象 {number : x}
    dispatch({payload}) 
    // dispatch(payload) 
  }
  return [state,setState]
}
// 調用
const [state, setState] = useStateSelf(initialState);
  return(
      <div> <p>自定義hooks</p> <p>{state.number}</p> <button onClick={() => setState({number: state.number + 1 })}>+</button> <button onClick={() => setState({number: state.number - 1 })}>-</button> </div>
  )
複製代碼

思路分析,首先參照useState的使用,須要傳入一個狀態(本例子只考慮數值,沒考慮對象)。返回一個數組,狀態和改變狀態的函數.

暫未更新完畢。會咱2天內更新完畢

相關文章
相關標籤/搜索