React總結來講(通訊/redux/路由/鉤子函數/三大解決邏輯共享方案(組件))

最全的(前端知識)彙總,以便自身複習

上一篇內容:JS/ES5->ES6

因爲篇幅過於長,知識體系過於龐雜,請謹慎學習, 轉述的我都有放置連接(難理解的也放了demo測試或者圖)javascript

技術規範(基礎瞭解,大佬儘可跳過)css

React

通訊問題

父-->子

父組件傳遞到子組件,符合了react的單向數據流理念,自上而下的傳遞propshtml

  • 父組件前端

    class Parent extends Component {
          
          ......省略
          
          render(){
              let {value}=this.state;
             return(  
                   <Child value={value} ></Child>
               )
          }
      }
    複製代碼
  • 子組件vue

    class Child extends Component {
          
          ......省略
          
          render(){
              let {value}=this.props;
             return(  
                  <span>{value}</span>
               )
          }
      }
    複製代碼

子-->父

依賴props的引用,經過父組件傳遞給子組件的回調函數【父組件屬性爲函數】來實現java

  • 父組件react

    class Parent extends Component {
    
              ......省略
            setValue = value => {
              this.setState({
                value,
              })
        }
    
        render() {
          return (
            <div>
            //父組件傳遞給子組件setValue的方法【回調函數 】
              <Child setValue={this.setValue} />
            </div>
          );
        }
      }
    複製代碼
  • 子組件webpack

    class Child extends Component {
    
        ......省略
          handleClick = () => {
              //接收父組件傳遞過來的方法
              const { setValue } = this.props;
              //子組件的value當作父組件的setValue參數回調回去
              setValue(this.value);
            }
            
            render() {
              return (
                <div>
                  我是Child
                  <div className="card">
                      //傳遞參數【回調】
                    <div className="button" onClick={this.handleClick}>通知</div>
                  </div>
                </div>
              );
            }
          }
    複製代碼

兄弟組件

  • 利用共有的Container【容器或者說父組件】git

    • Container(容器)github

      // container
        class Container extends Component {
          constructor() {
            super();
            this.state = {
              value: '',
            }
          }
      
          setValue = value => {
          //改變容器數據 
            this.setState({
              value,
            })
          }
      
          render() {
            return (
              <div>
              //A組件回調函數設置改變容器的值(子--》父通訊)
                <A setValue={this.setValue}/>
                //B組件獲取容器的數據(父--》子通訊)
                <B value={this.state.value} />
              </div>
            );
          }
        }
      複製代碼
    • A組件

      class A extends Component {
      
        。。。省略
          handleClick = () => {
            const { setValue } = this.props;
            setValue(this.value);
          }
      
          render() {
            return (
              <div className="card">
                <div className="button" onClick={this.handleClick}>通知</div>
              </div>
            )
          }
        }
      複製代碼
    • B組件

      const B = props => (
      
          return(
            <div className="card">
                {props.value}
              </div>
           )
      
        );
        export default B;
      複製代碼

跨級組件通訊(非父子):Context(16.x有全新的API)

  • 老版本Context

想要Context發揮做用【去實現跨級組件的通訊問題】,先解決什麼時候使用,避免濫用Context帶來的組件複用性變差的影響

  • Context就像javascript中的全局變量,只有真正全局的東西才適合放在context中

  • 頂層組件A

    //引入類型檢查 
       import PropTypes from 'prop-types';
       // Component A
       class A extends React.Component {
       // add the following method
         getChildContext() {
           return {
             user: this.props.user
           }
         }
         
         render() {
           <MiddleComponent />
         }
       }
       // add the following property
       A.childContextTypes = {
         user: PropTypes.string
       }
    複製代碼
    • 底層跨級組件D

      // Component D
               class D extends React.Component {
                 render() {
                   return <div>{this.context.user}</div>
                 }
               }
               // add the following property
               D.contextTypes = {
                 user: PropTypes.string
               }
      複製代碼
  • 注意事項

    • 底層跨級組件,須要聲明須要使用的Context屬性,才能訪問父組件Context對象的屬性,不然,即便屬性名沒寫錯,拿到的對象也是undefined
    • 隨着你的應用程序不斷增加,你能夠經過類型檢查捕獲大量錯誤,配置特定的 propTypes 屬性,React-proptypes的類型檢查
    • Context發揮做用,須要兩種組件:一個是Context生產者(Provider),一般是一個父節點------另外是一個Context的消費者(Consumer),一般是一個或者多個子節點。因此Context的使用基於生產者消費者模式
    • 確保Context是可控的,使用Context並沒有大礙,甚至若是可以合理的應用,Context其實能夠給React組件開發帶來很強大的體驗
    • 受到生命週期shouldComponentUpdate的影響

借鑑參考文:

聊一聊我對 React Context 的理解以及應用 -----Context-----React學習(10)—— 高階應用:上下文(Context)

  • 新版本Context

新版本的React context使用了Provider和Customer模式,和react-redux的模式很是像。在頂層的Provider中傳入value, 在子孫級的Consumer中獲取該值,而且可以傳遞函數,用來修改context

  • React.createContext的方式建立了一個Context實例

    export const themes = {
                 light: {
                   foreground: '#000000',
                   background: '#eeeeee',
                 },
                 dark: {
                   color: '#ffffff',
                   background: '#222222',
                 },
               };
    
               export const ThemeContext = React.createContext(
                 themes.dark // 默認值
               );
    複製代碼
  • 頂層組件

    import {ThemeContext} from './theme-context';
          class Message extends React.Component {
              render(){
                  return(
                      <ThemeContext.Provider>
                          //中間組件
                          <Title />
                      </ThemeContext.Provider>
                  );
              }
          }
    複製代碼
  • 中間組件(跨層級的通訊,這邊不處理數據【這裏要處理數據不須要context】)

    Title(props) {
            return (
              <div>
                <Text />
              </div>
            );
          }
    複製代碼
  • 消費數據組件 (底層跨級組件 )

    class Text extends React.Component {
               render () {
                  return (
                    <ThemeContext.Consumer>
                      {context => (
                        <h1 style={{ color: context.color,background: context.background}}>
                          {this.props.children}
                        </h1>
                      )}
                    </ThemeContext.Consumer>
                  );
                }
              }
    複製代碼
  • 瞭解點

    • Provider

      這裏的 Provider 相似 react-redux 中的 Provider 組件,用來注入全局的 data (容許 Consumer 訂閱 Context 的變化)。一個 Provider 能夠鏈接到多個 Consumer。

    • Consumer

      Consumer 組件,表示要消費 Provider 傳遞的數據(訂閱 Context 的響應組件)。當 Provider 發生變化的時候,全部的 Consumer 都會被 re-rendered

    • 你能夠在任何生命週期中訪問到它,包括 render 函數中

      class MyClass extends React.Component {
          componentDidMount() {
            let value = this.context;
            /* 在組件掛載完成後,使用 MyContext 組件的值來執行一些有反作用的操做 */
          }
          componentDidUpdate() {
            let value = this.context;
            /* ... */
          }
          componentWillUnmount() {
            let value = this.context;
            /* ... */
          }
          render() {
            let value = this.context;
            /* 基於 MyContext 組件的值進行渲染 */
          }
        }
        MyClass.contextType = MyContext;
      複製代碼
    • 消費多個 Context

      // Theme context,默認的 theme 是 「light」 值
       const ThemeContext = React.createContext('light');
      
       // 用戶登陸 context
       const UserContext = React.createContext({
         name: 'Guest',
       });
      
       class App extends React.Component {
         render() {
           const {signedInUser, theme} = this.props;
      
           // 提供初始 context 值的 App 組件
           return (
             <ThemeContext.Provider value={theme}>
               <UserContext.Provider value={signedInUser}>
                 <Layout />
               </UserContext.Provider>
             </ThemeContext.Provider>
           );
         }
       }
      
       function Layout() {
         return (
           <div>
             <Sidebar />
             <Content />
           </div>
         );
       }
      
       // 一個組件可能會消費多個 context
       function Content() {
         return (
           <ThemeContext.Consumer>
             {theme => (
               <UserContext.Consumer>
                 {user => (
                   <ProfilePage user={user} theme={theme} />
                 )}
               </UserContext.Consumer>
             )}
           </ThemeContext.Consumer>
         );
       }
      複製代碼
    • 在嵌套組件中更新 Context

      export const themes = {
                          light: {
                            foreground: '#000000',
                            background: '#eeeeee',
                          },
                          dark: {
                            color: '#ffffff',
                            background: '#222222',
                          },
                        };
                // 確保傳遞給 createContext 的默認值數據結構是調用的組件(consumers)所能匹配的!
                export const ThemeContext = React.createContext({
                  theme: themes.dark,
                  toggleTheme: () => {},
                });
            
        ------------------------------------------------------------------------------
      
        import {ThemeContext} from './theme-context';
      
        function ThemeTogglerButton() {
          // Theme Toggler 按鈕不只僅只獲取 theme 值,它也從 context 中獲取到一個 toggleTheme 函數
          return (
            <ThemeContext.Consumer>
              {({theme, toggleTheme}) => (
                <button
                  onClick={toggleTheme}
                  style={{backgroundColor: theme.background}}>
      
                  Toggle Theme
                </button>
              )}
            </ThemeContext.Consumer>
          );
        }
      
        export default ThemeTogglerButton;
      
        -------------------------------------------------------------------
      
            import {ThemeContext, themes} from './theme-context';
            import ThemeTogglerButton from './theme-toggler-button';
      
            class App extends React.Component {
              constructor(props) {
                super(props);
      
                //更新函數
                this.toggleTheme = () => {
                  this.setState(state => ({
                    theme:
                      state.theme === themes.dark
                        ? themes.light
                        : themes.dark,
                  }));
                };
      
                // State 也包含了更新函數,所以它會被傳遞進 context provider。
                this.state = {
                  theme: themes.light,
                  toggleTheme: this.toggleTheme,
                };
              }
      
              render() {
                // 整個 state 都被傳遞進 provider(傳遞映射給了ThemeContext)
                return (
                  <ThemeContext.Provider value={this.state}>
                    <Content />
                  </ThemeContext.Provider>
                );
              }
            }
      
            function Content() {
              return (
                <div>
                  <ThemeTogglerButton />
                </div>
              );
            }
      
            ReactDOM.render(<App />, document.root);
      複製代碼
    • 注意事項

    • 參考資料:

    React 16.3來了:帶着全新的Context API------ 全新的 React Context API-------React官方文檔

    Snipaste_2019-08-19_10-57-29.png

  • 經過發佈/訂閱者模式來實現(能夠本身實現/引入第三方庫別人寫好的)

PubSubJS【相似於Vue公交總站(只是vue他內部本身實現了)】

  • 訂閱(訂閱消息)PubSub.subscrib(名稱,函數)

    import PubSub from 'pubsub-js'
       PubSub.subscribe('delete',function(msg,data){})
    複製代碼
    • 示例

      componentDidMount(){  
         this.pubsub_token = PubSub.subscribe('PubSubmessage', (topic,message)=> {  
           this.setState({  
             increase: message  
           });  
         });  
       }  
      複製代碼
  • 發佈(發送消息)PubSub.publish(名稱,參數)

    import PubSub from 'pubsub-js'
       PubSub.publish('delete',data);
    複製代碼
    • 示例

      buttonDecrease(){  
            PubSub.publish('PubSubmessage', this.state.decrease);  
         }  
       <button onClick={this.buttonIncrease.bind(this)}>Increase</button> 
      複製代碼
  • 取消訂閱:PubSub.unsubscrib(名稱)

    import PubSub from 'pubsub-js'
       PubSub.unsubscribe(name);
    複製代碼
    • 示例

      componentWillUnmount(){  
           PubSub.unsubscribe(this.pubsub_token);
         }  
      複製代碼
  • Redux

Redux使用(React-redux)

Redux 入門教程(一):基本用法-做者: 阮一峯

"只有遇到 React 實在解決不了的問題,你才須要 Redux 。"

簡單說,若是你的UI層很是簡單,沒有不少互動,Redux 就是沒必要要的,用了反而增長複雜性

  • (組件角度)應用場景

    • 某個組件的狀態,須要共享
    • 某個狀態須要在任何地方均可以拿到
    • 一個組件須要改變全局狀態
    • 一個組件須要改變另外一個組件的狀態
  • Redux 的適用場景:多交互、多數據源。

    • 用戶的使用方式複雜

    • 不一樣身份的用戶有不一樣的使用方式(好比普通用戶和管理員)

    • 多個用戶之間能夠協做

    • 與服務器大量交互,或者使用了WebSocket

    • View要從多個來源獲取數據

    • 使用redux-saga

      從redux-thunk到redux-saga,本質都是爲了解決異步action的問題

      • 數據如何獲取?

        • 創建倉庫,把倉庫(數據事件)state在入口文件(頂層容器注入)

          1. createSagaMiddleware():函數是用來建立一個 Redux 中間件,將 Sagas 與 Redux Store 連接起來

          sagas 中的每一個函數都必須返回一個 Generator 對象,middleware 會迭代這個 Generator 並執行全部 yield 後的 Effect(Effect 能夠看做是 redux-saga 的任務單元)

        1. middleware.run(sagas, ...args):動態執行sagas,用於applyMiddleware階段以後執行sagas

          • 方式一:

            import {createStore, applyMiddleware} from 'redux'
              import createSagaMiddleware from 'redux-saga'
              import reducers from './reducers'
              import rootSaga from './rootSaga'
            
              // 建立一個saga中間件
              const sagaMiddleware = createSagaMiddleware()
            
              // 建立store
              const store = createStore(
                reducers,
                將sagaMiddleware 中間件傳入到 applyMiddleware 函數中
                applyMiddleware(sagaMiddleware)
              )
            
              // 動態執行saga,注意:run函數只能在store建立好以後調用
              sagaMiddleware.run(rootSaga)
            
              export default store
            複製代碼
            • 與之配合的入口文件

              import React from 'react';
                        import ReactDOM from 'react-dom';
                        import {Provider} from 'react-redux';
                        import store from './redux/store/store';
                        import App from './App'
              
                        ReactDOM.render(
                          <Provider store={store}>//屬性store隨意改的
                            <App />
                          </Provider>, document.getElementById('root')
                        );
              複製代碼
        • 方式二:

          //利用createStore把reducer數據中心的屬性或者方法映射到store,以後經過Provider傳遞給組件
               //applyMiddleware,添加中間件
               /*
               *applyMiddleware 函數的做用就是對 store.dispatch 方法進行加強和改造,
               *使得在發出 Action 和執行 Reducer 之間添加其餘功能。
               */
               import { createStore, applyMiddleware } from "redux";
          
               import createSagaMiddleware from "redux-saga";
          
               import logger from "redux-logger";
          
               import mySaga from "../sagas";
               import reducer from "../reducers";
          
               //實例化redux-saga的createSagaMiddleware建立一個saga方法中間件
               const sagamiddleware = createSagaMiddleware();
          
               //配置倉庫
               export default () => {
                 //sagamiddleware中間件加入到middlewaress數組
                 const middlewares = [sagamiddleware];
                 //React Native中有一個全局變量__DEV__用於指示當前運行環境是不是開發環境
                 if (__DEV__) {
                   //開發環境引入logger
                   middlewares.push(logger);
                 }
                 //引用中間件建立倉庫
                 const createStoreMiddleware = applyMiddleware(...middlewares)(createStore);
          
                 //建立數據中心鏈接倉庫
                 // const store = createStore(reducer, applyMiddleware(...middlewares));
                 const store = createStoreMiddleware(reducer);
                 // console.log(store);
                 //運行saga,或者這樣寫sagaMiddleware.run(mySaga),mySaga是引入的saga文件
                 sagamiddleware.run(mySaga);
          
                 return store;
               };
          複製代碼
          • 與之配合的入口文件

            import React from 'react';
                        import ReactDOM from 'react-dom';
                        import {Provider} from 'react-redux';
                        import storeConfig from './redux/store/store';
                        import App from './App'
            
                       let store = storeConfig();
                        
                        ReactDOM.render(
                          <Provider store={store}>  //屬性store隨意改的
                            <App />
                          </Provider>, document.getElementById('root')
                        );
            複製代碼
        • 行爲(或者事件)【派發任務】

          • 變量不分離---(saga中間件,異步請求再也不actions這裏處理)

            const News = {
                 GET_MANY_NEWS: "GET_MANY_NEWS"
               };
            
               //action這邊只定義(規範)全部的行爲類型type
            
               export function fetchNewData(parmas) {
                 return {
                   type: News.GET_MANY_NEWS,
                   parmas
                 };
               }
            複製代碼
          • 變量分離

            constants/xxx.ts

            export const BANNERARRAY: string = "BANNERARRAY"
            複製代碼

            actions/xxx.ts---(saga中間件,異步請求再也不actions這裏處理)

            import {BANNERARRA} from constants/xxx.ts
               
               export function fetchNewData(parmas) {
                       return {
                               type: BANNERARRA,
                               parmas
                              };
                        }
            複製代碼
        • 制定了行爲以後(設置該行爲到底作什麼異步動做?)【也就是這裏使用了中間件redux-saga】redux-saga文檔 /// 從redux-thunk到redux-saga實踐 /// 解決防抖/延遲等 Redux-saga學習筆記

          監聽 action 來執行有反作用的 task,以保持 action 的簡潔性。而且引入了 sagas 的機制和 generator 的特性,讓redux-saga 很是方便地處理複雜異步問題 redux-saga 是一個 redux 中間件,意味着這個線程能夠經過正常的 redux action 從主應用程序啓動,暫停和取消,它能訪問完整的 redux state,也能夠 dispatch redux action

          普及redux-saga的api

          Effect 是一個 javascript 對象,裏面包含描述反作用的信息,能夠經過 yield 傳達給 sagaMiddleware 執行

          在 redux-saga 世界裏,全部的 Effect 都必須被 yield 纔會執行,因此有人寫了 eslint-plugin-redux-saga 來檢查是否每一個 Effect 都被 yield。而且原則上來講,全部的 yield 後面也只能跟Effect,以保證代碼的易測性。

          例如:yield fetch(UrlMap.fetchData);
          
             應該用 call Effect : yield call(fetch, UrlMap.fetchData)
          複製代碼
        • 關於各個 經常使用Effect 的具體介紹:官方api

          1. take(pattern):等待 dispatch 匹配某個 action

            take函數能夠理解爲監聽將來的action,它建立了一個命令對象,告訴middleware等待一個特定的action, Generator會暫停,直到一個與pattern匹配的action被髮起,纔會繼續執行下面的語句,也就是說,take是一個阻塞的 effect

            function* watchFetchData() {
                        while(true) {
                          /* 監聽一個type爲 'FETCH_REQUESTED' 的action的執行,
                          直到等到這個Action被觸發,
                          纔會接着執行下面的 yield fork(fetchData)  語句
                          /?
                          yield take('FETCH_REQUESTED');
                          yield fork(fetchData);
                        }
                     }
            複製代碼
          2. put(action)

            put函數是用來發送action的 effect,你能夠簡單的把它理解成爲redux框架中的dispatch函數,當put一個action後,reducer中就會計算新的state並返回,注意: put 也是阻塞 effect

            export function* toggleItemFlow() {
                 let list = []
                 // 發送一個type爲 'UPDATE_DATA' 的Action,用來更新數據,參數爲 `data:list`
                 yield put({
                   type: actionTypes.UPDATE_DATA,
                   data: list
                 })
             }
            複製代碼
          3. call(fn,args)

            call函數你能夠把它簡單的理解爲就是能夠調用其餘函數的函數,它命令 middleware 來調用fn 函數, args爲函數的參數,注意: fn 函數能夠是一個 Generator 函數,也能夠是一個返回 Promise 的普通函數,call 函數也是阻塞 effect會等返回結果後執行後面的語句

            export const delay = ms => new Promise(resolve => setTimeout(resolve, ms))
            
             export function* removeItem() {
                   try {
                     // 這裏call 函數就調用了 delay 函數,delay 函數爲一個返回promise 的函數
                     return yield call(delay, 500)
                   } catch (err) {
                     yield put({type: actionTypes.ERROR})
                   }
             }
            複製代碼
          4. fork(fn, ...args)

            fork 函數和 call 函數很像,都是用來調用其餘函數的,可是fork函數是非阻塞函數,也就是說,程序執行完 yield fork(fn, args) 這一行代碼後,會當即接着執行下一行代碼語句,而不會等待fn函數返回結果

            import { fork } from 'redux-saga/effects'
            
             export default function* rootSaga() {
               // 下面的四個 Generator 函數會一次執行,不會阻塞執行
               yield fork(addItemFlow)
               yield fork(removeItemFlow)
               yield fork(toggleItemFlow)
               yield fork(modifyItem)
             }
            複製代碼
            1. select(selector, ...args)

              select 函數是用來指示 middleware調用提供的選擇器獲取Store上的state數據,你也能夠簡單的把它理解爲redux框架中獲取store上的 state數據同樣的功能 :store.getState()

              export function* toggleItemFlow() {
                     // 經過 select effect 來獲取 全局 state上的 `getTodoList` 中的 list
                     let tempList = yield select(state => state.getTodoList.list)
                }
              複製代碼
            2. all([...effects])

              建立一個 Effect 描述信息,用來命令 middleware 並行地運行多個 Effect,並等待它們所有完成。這是與標準的 Promise#all 至關對應的 API。

              import { fetchCustomers, fetchProducts } from './path/to/api'
                  import { all, call } from `redux-saga/effects`
              
                  function* mySaga() {
                    const [customers, products] = yield all([
                      call(fetchCustomers),
                      call(fetchProducts)
                    ])
                  }
              複製代碼
            3. cancel(task): 是一個無阻塞 Effect。也就是說,Generator 將在取消異常被拋出後當即恢復。

        • Saga 輔助函數

          • takeEvery

            1. 用來監聽action,每一個action都觸發一次,若是其對應是異步操做的話,每次都發起異步請求,而不論上次的請求是否返回。

            2. takeEvery 容許多個 fetchData 實例同時啓動,在某個特定時刻,咱們能夠啓動一個新的 fetchData 任務, 儘管以前還有一個或多個 fetchData 還沒有結束

              import { takeEvery } from 'redux-saga'
              
                     function* watchFetchData() {
                       yield* takeEvery("FETCH_REQUESTED", fetchData)
                     }
              複製代碼
          • takeLatest

            若是咱們只想獲得最新那個請求的響應(例如,始終顯示最新版本的數據),咱們可使用 takeLatest 輔助函數

            import { takeLatest } from 'redux-saga'
            
                  function* watchFetchData() {
                    yield* takeLatest('FETCH_REQUESTED', fetchData)
                  }
            複製代碼

            和takeEvery不一樣,在任什麼時候刻 takeLatest 只容許執行一個 fetchData 任務,而且這個任務是最後被啓動的那個,若是以前已經有一個任務在執行,那以前的這個任務會自動被取消

        • sagas

          import https from "@/api";
                  import { all, call, put, takeLatest } from "redux-saga/effects";
          
                  export function* newsList(actions: any) {
                    try {
                      const data = yield call(https.newsLists, actions.parmas);
                      yield put({
                        type: "GETSUCCESS",
                        data
                      });
                    } catch (err) {
                      yield put({
                        type: "GETFAIL"
                      });
                    }
                  }
          
                  export default function* root() {
                    // 監聽GET_MANY_NEWS行爲事件newsList
                    yield all([takeLatest("GET_MANY_NEWS", newsList)]);
                  }
          複製代碼
          • 多個saga統一執行

            import { all, fork } from "redux-saga/effects";
               import home from "./home";
               //暴露全部的saga
               function* rootSaga() {
                 yield all([
                   fork(home)
                 ]);
               }
               export default rootSaga;
            複製代碼
          • reducers處理數據中心

            const initState = {
                    newsList: []
                  };
            
                  //數據處理中心(經過方法/請求數據等都須要通過reducer)
                  //基本從actions來
                  const Newslist = (state = initState, actions: any) => {
                    //接收到Succsee,就把接收到的數據存放到newsList
                    // console.log(actions.data);
                    switch (actions.type) {
                      case "GETSUCCESS":
                        //合併數據到data裏
                        return { ...state, newsList: actions.data };
                      case "GETFAIL":
                        //合併數據到data裏
                        return state;
                      default:
                        return state;
                    }
                  };
            
                  export default Newslist;
                  
             + 多個reducer統一執行
             
                      import { combineReducers } from "redux";
                      import home from "./home";
            
                      //暴露到入口文件存放到倉庫
                      export default combineReducers({
                        home
                      });
            複製代碼
          • 組件裏該如何使用呢?英文官方文檔

            • 法一 bindActionCreators(commonAction, dispatch)映射到actions,映射到fetchNewData行爲,傳遞參數,調用函數事件,Data經過this.props.Data獲取【commonAction能夠分解,拿出須要的事件】

              //獲取數據(新聞)
                     getList = (page: number) => {
                       //這邊只發起請求
                       const {
                         actions: { fetchNewData }
                       } = this.props;
                       fetchNewData({ page, limit: 8 });
                     };
              
                   const mapStateToProps = (state, props) => ({ 
                       Data: state.home.newsList //傳遞數據到Data,子組件接收到data
                       })
                       
                   //commonAction是定義的行爲
                   const mapDispatchToProps = dispatch => (
                       {actions: bindActionCreators(commonAction, dispatch)
                   })
                   
                   //withRouter添加history
                   export default withRouter(connect(
                           mapStateToProps, mapDispatchToProps
                           )(Cart))
              複製代碼
            • 方式二

              1. return bindActionCreators(commonAction, dispatch)獲取到全部的行爲方法,而後就能夠是使用全部方法

              2. commonAction能夠把裏面的事件須要的拿出來import {fetchNewData} from 'xxx'

              3. return bindActionCreators({fetchNewData}, dispatch)

                //獲取數據(新聞)
                                    getList = (page: number) => {
                                       //這邊只發起請求
                                       fetchNewData({ page, limit: 8 });
                                     };
                
                       const mapStateToProps = (state, props) => ({ 
                           Data: state.home.newsList //傳遞數據到Data,子組件接收到data
                           })
                           
                       //commonAction是定義的行爲
                       const mapDispatchToProps = dispatch => (
                            return bindActionCreators(commonAction, dispatch)
                       )
                       
                       //withRouter添加history
                       export default withRouter(connect(
                               mapStateToProps, mapDispatchToProps
                               )(Cart))
                複製代碼
      • reudx-thunk reudx-saga惟一區別actions的解耦性

        • 法一

          //方法容許傳遞參數
                 export const getListData=(payload)=>{
                         return{
                             type:types.GETLISTS,
                             payload:payload
                         }
                   }
          
                  export function  fetchData(params={page:1}){
                    //dispath函數看成參數
                         return (dispath)=>{
                           fetch(
                  `http://localhost:3000/person?_page=${params.page}&_limit=4`).then(res=>{
                                 return res.json();
                             }).then(data=>{
                                 //異步dispath
                                  dispath(getListData(data))
                               })
                           }
                      }
          複製代碼
          • 法二(異步直接耦合)

            import { BANNERARRAY } from "../constants/findSong";
              import http from "@/https/index.ts";
            
                  // 獲取發現頁輪播
                  export const getBannerArray = () => {
                    return dispatch => {
                      http.findBanner().then(res => {
                        if (res.statusCode == 200) {
                          let bannerData = res.data.banners;
                          dispatch({
                            type: BANNERARRAY,
                            payload: {
                              bannerData
                            }
                          });
                        }
                      });
                    };
                  };
            複製代碼
        • 入口文件中間件換成redux-thunk就行

redux的reateStore,combineReducers,bindActionCreators,applyMiddleware源碼分析 /// 探究redux源碼-衍生-中間件思想 /// Redux源碼深度解讀

Immutable(Immutable.js)reducer會用到多一點

Immutable Data 就是一旦建立,就不能再被更改的數據。對 Immutable 對象的任何修改或添加刪除操做都會返回一個新的 Immutable 對象。Immutable 實現的原理是 Persistent Data Structure(持久化數據結構),也就是使用舊數據建立新數據時,要保證舊數據同時可用且不變。同時爲了不 deepCopy 把全部節點都複製一遍帶來的性能損耗,Immutable 使用了 Structural Sharing(結構共享),即若是對象樹中一個節點發生變化,只修改這個節點和受它影響的父節點,其它節點則進行共享。【來減小內存佔用(和垃圾回收的失效)】

s

Immutable.js 以及在 react+redux 項目中的實踐

Immutable總結

  • formJS(),將 JavaScript Object 和 Array 完全轉換爲 Immutable Map 和 List

  • is(),與 Object.is() 相似都是對值的比較,但它會將 Immutable Iterable 視爲值類型數據而不是引用類型數據,若是兩個 Immutable Iterable 的值相等,則返回 true。與 Object.is() 不一樣的是,is(0, -0) 的結果爲 true

  • List,有序索引集,相似於 JavaScript 中的 Array

  • Map,無序 Iterable,讀寫 Key 的複雜度爲 O(log32 N)

  • OrderedMap,有序 Map,排序依據是數據的 set() 操做

  • Set,元素爲獨一無二的集合,添加數據和判斷數據是否存在的複雜度爲 O(log32 N)

  • OrderedSet,有序 Set,排序依據是數據的 add 操做。

  • Stack,有序集合,且使用 unshift(v) 和 shift() 進行添加和刪除操做的複雜度爲 O(1)

  • Range(),返回一個 Seq.Indexed 類型的數據集合,該方法接收三個參數 (start = 1, end = infinity, step = 1),分別表示起始點、終止點和步長,若是 start 等於 end,則返回空的數據結合

  • Repeat(),返回一個 Seq.indexed 類型的數據結合,該方法接收兩個參數 (value,times),value 表示重複生成的值,times 表示重複生成的次數,若是沒有指定 times,則表示生成的 Seq 包含無限個 value

  • Record,用於衍生新的 Record 類,進而生成 Record 實例。Record 實例相似於 JavaScript 中的 Object 實例,但只接收特定的字符串做爲 key,且擁有默認值

  • Seq,序列(may not be backed by a concrete data structure)

  • Iterable,能夠被迭代的 (Key, Value) 鍵值對集合,是 Immutable.js 中其餘全部集合的基類,爲其餘全部集合提供了 基礎的 Iterable 操做函數(好比 map() 和 filter)

  • Collection,建立 Immutable 數據結構的最基礎的抽象類,不能直接構造該類型

React提供了PureRenderMixin(新版本棄用)和React.PureComponent,可是它們只提供了prop和state的淺比較方式,並不能深層比較新舊屬性是否相同。

爲何不實現深層比較呢?由於若是prop或者state比較複雜,深層比較性能會不好。因此只作淺比較是一個相對比較合理的折中。

Immutable能夠很好地解決這個問題,不只能夠深層比較數據是否發生變化,並且效率很高。

新的shouldComponentUpdate實現以下:

import {is} from 'immutable';

        shouldComponentUpdate(nextProps = {}, nextState = {}) {
            const thisProps = this.props || {};
            const thisState = this.state || {};

            if (Object.keys(thisProps).length !== Object.keys(nextProps).length ||
                Object.keys(thisState).length !== Object.keys(nextState).length) {
                return true;
            }

            for (const key in nextProps) {
                if (!is(thisProps[key], nextProps[key])) {
                    return true;
                }
            }

            for (const key in nextState) {
                if (!is(thisState[key], nextState[key])) {
                    return true;
                }
            }
            return false;
        }
複製代碼

React 路由(倆個版本的路由)

  • react-router路由@3.2.3【npm i react-router-dom react-router@3.2.3 -s】

    • router/index.js (路由部分)

      1. 懶加載,經過React模塊的Suspense與lazy

      2. Router包裹Route,Route:屬性path與component->與相應組件對應

        • 路由器Router就是React的一個組件【Router組件自己只是一個容器,真正的路由要經過Route組件定義】

          import { Router, Route, hashHistory } from 'react-router';
          
             render((
               <Router history={hashHistory}>
                 <Route path="/" component={App}/>
               </Router>
             ), document.getElementById('app'));
          複製代碼
      3. 嵌套路由,Route嵌套Route

        • 路由

          <Router history={hashHistory}>
               <Route path="/" component={App}>
                 <Route path="/repos" component={Repos}/>
                 <Route path="/about" component={About}/>
               </Route>
             </Router>
          複製代碼
        • App組件(this.props.children表示嵌套的路由)

          render() {
                    return <div>
                      {this.props.children}
                    </div>
                  }
          複製代碼
      4. 主頁顯示,不須要path,須要引入react-router--IndexRoute

        • IndexRoute 組件:指定默認狀況下加載的子組件。你能夠把IndexRoute想象成某個路徑的index.html

          import React,{Suspense} from "react";
                 import {Router,Route,hashHistory,IndexRoute} from 'react-router'
                 //一塊兒引入,經過index.js
                 // import {Home,About,App,Repo,Rep} from '../components/'
          
                 //懶加載拆分
                 const App=React.lazy(()=>import('../components/App'));
                 const Home=React.lazy(()=>import('../components/Home'));
                 const About=React.lazy(()=>import('../components/About'));
                 const Repo=React.lazy(()=>import('../components/Repo'));
                 const Rep=React.lazy(()=>import('../components/Rep'));
          
          
                 export default class Index extends React.Component{
                     render(){
                         return(
                             <Suspense fallback={<div>Loding....</div>}>
                                 <Router history={hashHistory}>
                                     <Route path="/" component={App}>
                                         {/* 主頁設置爲默認,利用IndexRoute */}
                                         <IndexRoute component={Home} />
                                         <Route path="/About" component={About}/>
                                         <Route path="/repo" component={Repo}>
                              <Route path="/repo/:username/:reponame" component={Rep}/>
                                         </Route>
                                     </Route>
                                 </Router>
                             </Suspense>
                         )
                     }
                 }
          
              ------------------------------------------------------------------
          
             export App from './App.js'
             export Home from './Home.js'
             export About from './About.js'
             export Repo from './Repo.js'
             export Rep from './Rep.js'
          複製代碼
    • 通配符(path屬性可使用通配符)

      <Route path="/hello/:name">
         // 匹配 /hello/michael
         // 匹配 /hello/ryan
      
         <Route path="/hello(/:name)">
         // 匹配 /hello
         // 匹配 /hello/michael
         // 匹配 /hello/ryan
      
         <Route path="/files/*.*">
         // 匹配 /files/hello.jpg
         // 匹配 /files/hello.html
      
         <Route path="/files/*">
         // 匹配 /files/ 
         // 匹配 /files/a
         // 匹配 /files/a/b
      
         <Route path="/**/*.jpg">
         // 匹配 /files/hello.jpg
         // 匹配 /files/path/to/file.jpg
      複製代碼
    • Link:Link組件用於取代a元素,生成一個連接,容許用戶點擊後跳轉到另外一個路由。它基本上就是a元素的React 版本,能夠接收Router的狀態。

      • activeClassName/activeStyle,能夠設置選中高亮【別忘了設置css】,精確匹配OnlyActiveOnIndex

        render() {
                    return <div>
                      <ul role="nav">
                        <Link to="/about" activeStyle={{color: 'red'}}>About</Link>
                        <Link to="/repos" activeClassName="active">Repos</Link>
                      </ul>
                    </div>
                  } 
        複製代碼
      • 封裝Link

        import {Link} from 'react-router';
              import React from 'react'
        
              const NavLink =(props)=>{
                  return(
                      <Link {...props} activeClassName="active" ></Link>
                  )
              }
        
              export default NavLink;
        複製代碼
    • IndexLink:若是連接到根路由/,不要使用Link組件,而要使用IndexLink組件

      • 根路由來講,activeStyle和activeClassName會失效,或者說老是生效,由於/會匹配任何子路由。而IndexLink組件會使用路徑的精確匹配

        <IndexLink to="/" activeClassName="active">
                             Home
                           </IndexLink>
        複製代碼
    • Redirect 組件:路由的跳轉,即用戶訪問一個路由,會自動跳轉到另外一個路由

      <Route path="inbox" component={Inbox}>
              {/* 從 /inbox/messages/:id 跳轉到 /messages/:id */}
              <Redirect from="messages/:id" to="/messages/:id" />
            </Route>
            如今訪問/inbox/messages/5,會自動跳轉到/messages/5。
      複製代碼
    • IndexRedirect 組件:訪問根路由的時候,將用戶重定向到某個子組件(第一次訪問不進入主頁)

      <Route path="/" component={App}>
             <IndexRedirect to="/welcome" />
             <Route path="welcome" component={Welcome} />
             <Route path="about" component={About} />
           </Route>
           上面代碼中,用戶訪問根路徑時,將自動重定向到子組件welcome。
      複製代碼
      • App

        import React,{Component} from "react";
               import NavLink from './NavLink'
               import {IndexLink} from 'react-router'
        
               class App extends Component{
                 
                 render(){
                   console.log(this.props)
                   return (
                     <div style={{width:'100%',height:'50px',background:'#cdcdcd'}}>
                       導航
                       {/* 設置主頁連接 */}
                       <IndexLink to="/" activeClassName="active">home</IndexLink>
                       <NavLink to="/about">about</NavLink>
                       <NavLink to="/repo">repo</NavLink>
                       <hr/>
                       {/* 相似slot的顯示子組件的,也就是路由,嵌套路由下的東西 */}
                       <h3>如下是內容</h3>
                       {this.props.children}
                     </div>
                     )
                 }
               }
        
               export default App;
        複製代碼
      • this.props.params能獲取到路由參數

      • hashHistory.push():跳轉組件

      • histroy 屬性

        • hashHistory,路由將經過URL的hash部分(#)切換
        • browserHistory,瀏覽器的路由就再也不經過Hash完成了,而顯示正常的路徑example.com/some/path
        • createMemoryHistory主要用於服務器渲染。它建立一個內存中的history對象,不與瀏覽器URL互動
      • 路由的鉤子

        • 進入路由【onEnter鉤子還能夠用來作認證】

          <Route path="inbox" component={Inbox}>
                   <Route
                     path="messages/:id"
                     onEnter={
                       ({params}, replace) => replace(`/messages/${params.id}`)
                     } 
                   />
                 </Route>
          複製代碼
    • react-router路由4x---5x 【npm i react-router-dom react-router -s】基本使用

      • main.js--入口文件

        1. 須要用到react-router-dom--HashRouter/BrowserRouter倆種模式

        2. Switch用到switch來切換路由,包裹起來

        3. exact,路由須要精確匹配

        4. 重定向,不給path

        5. 利用map提取單獨拿出來的路由數組

          import React from "react";
               import ReactDOM from "react-dom";
               import "./main.css";
               import {Home,BasicRouting,Nav,Blocking,Miss,Address,Query,Recursive} from './components'
               import {HashRouter as Router,Route,Switch} from 'react-router-dom'
               // import routes from './router'
          
               const Index=()=>{
                   return(
                       <Router>
                           <Nav />
                           <Switch>
          
                           {/* {
                               routes.map((route,idx)=>{
                                   return <Route path={route.path} component={route.component} exact={route.exact} />
                               })
                           } */}
          
                               <Route path="/" component={Home} exact/>
                               <Route path="/basic" component={BasicRouting} />
                               <Route path="/block" component={Blocking} />
                               <Route path="/miss" component={Miss} />
                               <Route path="/query" component={Query} />
                               <Route path="/recursive" component={Recursive} />
                               <Route component={Address} />
                           </Switch>
                       </Router>
                   )
               }
          
               ReactDOM.render(<Index />, document.getElementById("app")
          複製代碼
        • 提取路由

          import React from "react";
                   import {Home,BasicRouting,Blocking,Miss,Query,Recursive} from './components'
          
                   module.exports=[
                       {
                           path:'/',
                           component:Home,
                           exact:true
                       },
                       {
                           path:'/basic',
                           component:BasicRouting
                       },
                       {
                           path:'/block',
                           component:Blocking
                       },
                       {
                           path:'/miss',
                           component:Miss
                       },
                       {
                           path:'/query',
                           component:Query
                       },
                       {
                           path:'/recursive',
                           component:Home,
                           exact:Recursive
                       }
                   ]
          複製代碼
          • 組件 Route 屬性 strict 末尾斜槓的匹配

            ====》路由請求末尾必須帶 /

            <Route strict path="/about/" component={About} />
            複製代碼
          • 組件 Route 屬性 exact 徹底匹配==

            ====》exact=false 的時候 path 等於 /about /about/me 都能匹配 ====》exact=true 的時候 只匹配 path 等於 /about

            <Route path="/about" component={About} />
            複製代碼
          • 組件 Link:生成路由連接

          • 組件 NavLink:生成路由連接的基礎上,若是是當前路由設置激活樣式【activeClassName="selected"】

            <div style={{width:'100%',height:'50px',background:'#cdcdcd'}}>
                       導航
                       <NavLink to="/" activeClassName="active" exact>Home</NavLink>
                       <NavLink to="/basic">BasicRouting</NavLink>
                       <NavLink to="/block">Blocking</NavLink>
                       <NavLink to="/miss">Miss</NavLink>
                       <NavLink to="/query">Query</NavLink>
                       <NavLink to="/recursive">Recursive</NavLink>
                     </div>
            複製代碼
          • 組件 Switch:只渲染出第一個與當前訪問地址匹配的 Route 或 Redirect

          • 組件 Redirect路由重定向:

            <Switch>
                     <Redirect from='/users/:id' to='/users/profile/:id'/>
                     <Route path='/users/profile/:id' component={Profile}/>
                   </Switch>
                   //當請求 /users/:id 被重定向去 '/users/profile/:id'
            複製代碼
          • 組件 Prompt:當用戶離開當前頁面前作出一些提示

            import React,{Component} from 'react'
                  import {Prompt} from "react-router-dom"
            
                  export default class Blocking extends Component{
                      render(){
                          return(
                              <div style={{width:'100%',height:'50px',background:'#cdcdcd'}}>
                                      <h1>Blocking</h1>
                                      <Prompt message="你肯定要離開嗎"/>
                              </div>
                          )
                      }
                  }
            複製代碼
          • 路由傳遞參數(設參/傳參)

            <NavLink to={{
                           pathname:this.props.match.url+"/level3",
                          search:"?aaa=222&bbb=1111"
                           }}>level3</NavLink>
            複製代碼
          • 獲取路由參數(接參使用參數)

            this.props.location
                   this.props.match.params
            複製代碼
          • 嵌套組件

            import React,{Component} from 'react'
                   import {Route,NavLink} from 'react-router-dom'
                   import {Content,Address} from './index'
            
                   export default class BasicRouting extends Component{
                       render(){
                           return(
                             <div style={{width:'100%',height:'250px',background:'#cdcdcd'}}>
                                  <ul>
                           <li><NavLink to={this.props.match.url+"/adc/level1"}>level1</NavLink></li>
                           <li><NavLink to={this.props.match.url+"/level2"}>level2</NavLink></li>
                           <li><NavLink to={this.props.match.url+"/adc/level3"}>level3</NavLink></li>
                                 </ul>
            
                           <Route path={`${this.props.match.url}/adc/:level`} component={Content} />
                               </div>
                           )
                       }
                   }
            複製代碼
          • 遞歸自調用本身(嵌套本身)

生命週期鉤子函數

詳解React生命週期(包括react16版)

clipboard.png

  • React 15.x

    • 初始化(initialization)

      • getDefaultProps(設置默認props)---->getInitialState(初始化state)

        • getDefaultProps

          1. 這個方法只會調用一次,該組件類的全部後續應用,getDefaultPops 將不會再被調用,其返回的對象能夠用於設置默認的 props

          2. getInitialState 和 getDefaultPops 的調用是有區別的,getDefaultPops 是對於組件類來講只調用一次,後續該類的應用都不會被調用,而 getInitialState 是對於每一個組件實例來說都會調用,而且只調一次。

            getDefaultProps: function(){
                      return {
                          name: 'pomy',
                          git: 'dwqs'
                      }
                  }
            複製代碼
        • getInitialState

          1. 對於組件的每一個實例來講,這個方法的調用**有且只有一次,**用來初始化每一個實例的 state,在這個方法裏,能夠訪問組件的 props。

            getInitialState: function() {
                         return {liked: false};
                       },
            複製代碼
    • 數據掛載(當組件在客戶端被實例化)

      • componentWillMount 首次渲染執行前當即調用且僅調用一次。若是在這個方法內部調用 setState 並不會觸發從新渲染,這也是在 render 方法調用以前修改 state 的最後一次機會

      • render(返回的結果並非真正的DOM元素,而是一個虛擬的表現,相似於一個DOM tree的結構的對象)

        該方法會建立一個虛擬DOM,用來表示組件的輸出。對於一個組件來說,render方法是惟一一個必需的方法。render方法須要知足下面幾點:

        1. 只能經過 this.props 和 this.state 訪問數據(不能修改)
        2. 能夠返回 null,false 或者任何React組件
        3. 只能出現一個頂級組件,不能返回一組元素
        4. 不能改變組件的狀態
        5. 不能修改DOM的輸出
      • componentDidMount:

        該方法被調用時,已經渲染出真實的 DOM,能夠再該方法中經過 this.getDOMNode() 訪問到真實的 DOM(推薦使用 ReactDOM.findDOMNode())

    • 數據更新過程(組件已經渲染好而且用戶能夠與它進行交互,好比鼠標點擊,手指點按,或者其它的一些事件,致使應用狀態的改變)

      • componentWillReceiveProps

        組件的 props 屬性能夠經過父組件來更改,這時,componentWillReceiveProps 未來被調用。能夠在這個方法裏更新 state,以觸發 render 方法從新渲染組件

      • shouldComponentUpdate:肯定組件的 props 或者 state 的改變不須要從新渲染

      • componentWillUpdate

        這個方法和 componentWillMount 相似,在組件接收到了新的 props 或者 state 即將進行從新渲染前,componentWillUpdate(object nextProps, object nextState) 會被調用,注意不要在此方面裏再去更新 props 或者 state

      • render

      • componentDidUpdate

        這個方法和 componentDidMount 相似,在組件從新被渲染以後,componentDidUpdate(object prevProps, object prevState) 會被調用。能夠在這裏訪問並修改 DOM。

    • 銷燬時

      • componentWillUnmount

        每當React使用完一個組件,這個組件必須從 DOM 中卸載後被銷燬,此時 componentWillUnmout 會被執行,完成全部的清理和銷燬工做,在 conponentDidMount 中添加的任務都須要再該方法中撤銷,如建立的定時器或事件監聽器。

    • 當再次裝載組件時,如下方法會被依次調用

      1. getInitialState
      2. componentWillMount
      3. render
      4. componentDidMount
  • React 16.3

    • 初始化(initialization)

      • static defaultProps(props初始化)---》constructor(state初始化)

        • static defaultProps

          static defaultProps={
                         age:'aa'
                 }
          複製代碼
        • constructor

          constructor(props) {
                        super(props);
                         this.state={
                                color:"white"
                                    }
                          }
          複製代碼
    • 數據掛載:-----與React 15x同樣

    • 數據更新的時候:-----與React 15x同樣

    • 銷燬時:-----與React 15x同樣

    • 當再次裝載組件時,如下方法會被依次調用

      1. constructor
      2. componentWillMount
      3. render
      4. componentDidMount
  • React-- 16.4

    • 數據初始化:-----與React 15.3同樣

    • 數據掛載(當組件在客戶端被實例化)

      • static getDerivedStateFromProps

        1. 在組件建立時和更新時的render方法以前調用,它應該返回一個對象來更新狀態,或者返回null來不更新任何內容

        2. 即 state 的值在任什麼時候候都取決於 props

          getSnapshotBeforeUpdate(prevProps, prevState)
          複製代碼
        3. 派生狀態會致使代碼冗餘,並使組件難以維護。 確保你已熟悉這些簡單的替代方案:

          1. 若是你須要執行反作用(例如,數據提取或動畫)以響應 props 中的更改,請改用 componentDidUpdate。

          2. 若是隻想在 prop 更改時從新計算某些數據,請使用 memoization helper 代替。瞭解JavaScript中的Memoization以提升性能,再看React的應用

          3. 若是你想在 prop 更改時「重置」某些 state,請考慮使組件徹底受控或使用 key 使組件徹底不受控 代替。受控組件與非受控組件 /// React組件的受控和非受控

      • render(返回的結果並非真正的DOM元素,而是一個虛擬的表現,相似於一個DOM tree的結構的對象)

        該方法會建立一個虛擬DOM,用來表示組件的輸出。對於一個組件來說,render方法是惟一一個必需的方法。render方法須要知足下面幾點:

        1. 只能經過 this.props 和 this.state 訪問數據(不能修改)
        2. 能夠返回 null,false 或者任何React組件
        3. 只能出現一個頂級組件,不能返回一組元素
        4. 不能改變組件的狀態
        5. 不能修改DOM的輸出
      • componentDidMount:

        該方法被調用時,已經渲染出真實的 DOM,能夠再該方法中經過 this.getDOMNode() 訪問到真實的 DOM(推薦使用 ReactDOM.findDOMNode())

    • 數據更新的時候

      • static getDerivedStateFromProps

      • shouldComponentUpdate:肯定組件的 props 或者 state 的改變不須要從新渲染

      • render

      • getSnapshotBeforeUpdate

        getSnapshotBeforeUpdate() 被調用於render以後,能夠讀取但沒法使用DOM的時候。它使您的組件能夠在可能更改以前從DOM捕獲一些信息(例如滾動位置)。今生命週期返回的任何值都將做爲參數傳遞給componentDidUpdate()。

      • componentDidUpdate

        這個方法和 componentDidMount 相似,在組件從新被渲染以後,componentDidUpdate(object prevProps, object prevState) 會被調用。能夠在這裏訪問並修改 DOM。

  • React 16.8.6

    • props只接受-----static defaultProps

    • useState-------constuctor this.state

    • useEffect---------componentDidMount,componentDidUpdate,

    • componentDidMount,componentDidUpdate,和 componentWillUnmount

    • React.memo-------shouldComponentUpdate

      //它幫助咱們控制什麼時候從新渲染組件Character
                //檢查接下來的渲染是否與前一次的渲染相同,若是二者是同樣的,那麼就會保留上一次的渲染結果
              export default React.memo(Character,(prevProps,nextProps)=>{
              return nextProps.selectedChar===prevProps.selectedChar
            });
      複製代碼

如何使用請看hooks

高階組件(HOC)- higher-order components

React組件設計實踐總結04 - 組件的思惟

面試系列之二:你真的瞭解React嗎(上)如何設計組件以及重要的生命週期

首先要明白咱們爲何須要高階組件:【無法操做組件中的組件的】

高階組件(HOC)是 React 中用於複用組件邏輯的一種高級技巧。HOC 自身不是 React API 的一部分,它是一種基於 React 的組合特性而造成的設計模式。

具體而言,高階組件是參數爲組件,返回值爲新組件的函數

  • 官方文檔規範

HOC 不會修改傳入的組件,也不會使用繼承來複制其行爲。相反,HOC 經過將組件包裝在容器組件中來組成新組件。HOC 是純函數,沒有反作用。

HOC 不須要關心數據的使用方式或緣由,而被包裝組件也不須要關心數據是怎麼來的 文檔

不要試圖在 HOC 中修改組件原型(或以其餘方式改變它)文檔

HOC 爲組件添加特性。自身不該該大幅改變約定。HOC 返回的組件與原組件應保持相似的接口。 文檔

最大化可組合性 文檔

包裝顯示名稱以便輕鬆調試 文檔

  • 官方文檔注意事項

動態調用 HOC。你能夠在組件的生命週期方法或其構造函數中進行調用。【不要在 render 方法中使用 HOC】文檔

務必複製靜態方法 文檔

Refs 不會被傳遞 Refs 不會被傳遞

Snipaste_2019-08-20_16-08-11.png

  • 先來一個最簡單的高階組件

    import React, { Component } from 'react';
                         import simpleHoc from './simple-hoc';
    
                         class Usual extends Component {
                           render() {
                             console.log(this.props, 'props');
                             return (
                               <div>
                                 Usual
                               </div>
                             )
                           }
                         }
                         export default simpleHoc(Usual);
                    
                    ------------------------------------------------------------------
                     import React, { Component } from 'react';
    
                     const simpleHoc = WrappedComponent => {
                       console.log('simpleHoc');
                       return class extends Component {
                         render() {
                           return <WrappedComponent {...this.props}/>
                         }
                       }
                     }
                     export default simpleHoc;
    複製代碼

    組件Usual經過simpleHoc的包裝,打了一個log... 那麼形如simpleHoc就是一個高階組件了,經過接收一個組件class Usual,並返回一個組件class。

  • 兩種形式(倆種形式能作到什麼)

    • 屬性代理(Props Proxy):【操做props/refs獲取組件實例/抽離state/用其餘元素包裹 WrappedComponent(包裝組件)】

      • 操做 props【讀取、添加、編輯、刪除】

        • 添加props

          import React, { Component } from 'react';
          
           const propsProxyHoc = WrappedComponent => 
               class PP extends Component {
          
               const newProps = {
                      uid,
                  };
          
                 render() {
                   return (<WrappedComponent
                     {...this.props}
                     {...newProps}
                   />);
                 }
           };
           export default propsProxyHoc;
          複製代碼
        • 刪除props

          import React, { Component } from 'react';
          
           const propsProxyHoc = WrappedComponent => 
               class PP extends Component {
          
               const  {uid,...otherProps}=this.props;
          
                 render() {
                   return (<WrappedComponent
                     {...otherProps}
                   />);
                 }
           };
           export default propsProxyHoc;
          複製代碼
      • refs獲取組件實例

        import React, { Component } from 'react';
        
           const propsProxyHoc = WrappedComponent => 
               class PP extends Component {
        
        
                 render() {
                   return (<WrappedComponent
                     {...this.props}
                     ref={instanceComponent=>this.instanceComponent=instanceComponent}
                   />);
                 }
           };
           export default propsProxyHoc;
        複製代碼
      • 抽離state:將全部的狀態的管理交給外面的容器組件,這個模式就是 抽取狀態 外面的容器就是這個高階組件(state【包括事件】在高階組件裏)-------------這裏用的比較多的就是react處理表單的時候

      @xxxx--》看react修飾器 用修飾器優雅的使用高階組件

      -------固然兼容性是存在問題的,一般都是經過babel去編譯的。 babel提供了plugin,高階組件用的是類裝飾器,因此用transform-decorators-legacy babel

      + 高階組件
              
             import React, { Component } from 'react';
            constHocContainer = (WrappedComponent) =>
                class PP extends React.Component {
                constructor(props) {
                   super(props)
                    this.state = {
                        name: ''
                    }
                }
                onNameChange = (event) => {
                    this.setState({
                        name: event.target.value
                    })
                }
                render() {
                    const newProps = {
                        name: {
                            value: this.state.name,
                            onChange: this.onNameChange
                        }
                    }
                    return <WrappedComponent {...this.props} {...newProps} />
                    }
                }
      + 組件    
      
                import React, { Component } from 'react';
                import HocContainer from 'xxxx' 
                @HocContainer
                classSampleComponent extends React.Component {
                render() {
                    return <input name="name" {...this.props.name}/>
                    }
                }
      複製代碼

      這裏咱們把state,onChange等方法都放到HOC裏,實際上是聽從的react組件的一種規範,子組件簡單,傻瓜,負責展現,邏輯與操做放到Container

      • 用其餘元素包裹 WrappedComponent(包裝組件) 爲了封裝樣式、佈局或別的目的,你能夠用其它組件和元素包裹 WrappedComponent

        constHocStyleComponent = (WrappedComponent, style) =>
              class PP extends React.Component {
              render() {
                  return (
                      <div style={style}>
                          <WrappedComponent {...this.props} {...newProps} />
                      </div>
                  )
              }
              }
        複製代碼

        這樣子使用:

        importHocStyleComponent from './HocStyleComponent';
         const colorSytle ={color:'#ff5555'}
         const newComponent = HocStyleComponent(SampleComponent, colorSytle);
        複製代碼

      代理方式下WrappedComponent會經歷一個完整的生命週期,產生的新組件和參數組件是兩個不一樣的組件,一次渲染,兩個組件都會經歷各自的生命週期 在繼承方式下,產生的新組件和參數組件合二爲一,super.render只是生命週期中的函數,變成一個生命週期

    • 繼承方式

      • 【操縱生命週期(渲染劫持)】

        • 高階組件:繼承方式下,產生的新組件和參數組件合二爲一,super.render只是生命週期中的函數,變成一個生命週期

          import * as React from 'react';
            constHocComponent = (WrappedComponent) =>
                classMyContainer extends WrappedComponent {
                    render() {
                        if (this.props.time && this.state.success) {
                            return super.render()//劫持了render函數
                        }
                    return <div>倒計時完成了...</div>
                }
            }
          複製代碼
        • 組件

          import * as React from 'react';
                importHocComponent from './HocComponent'
                @HocComponent
                classDemoComponent extends React.Component {
                    constructor(props) {
                        super(props);
                        this.state = {
                            success: true,
                        };
                    }
                render() {
                    return <div>我是一個組件</div>
                    }
                }
                exportdefaultDemoComponent;  
          複製代碼

          ss

        • 操縱prop【也能夠控制state,限制這樣作,可能會讓WrappedComponent組件內部狀態變得一團糟。建議能夠經過從新命名state,以防止混淆】

          constHOCPropsComponent = (WrappedComponent) =>
                   class PP extends WrappedComponent {
                   render() {
                       const elementsTree = super.render();
                    let newProps = {
                           color: (elementsTree && elementsTree.type === 'div') ? '#fff' : '#ff5555'
                   };
                   const props = Object.assign({}, elementsTree.props, newProps)
                   const newElementsTree = React.cloneElement(elementsTree, props, elementsTree.props.children)
                   return newElementsTree
                       }
                   }
          複製代碼
  • 彩蛋recompose庫:它提供了不少頗有用的高階組件(小工具),並且也能夠優雅的組合它們。

  • Render Props

    Render Props(Function as Child) 也是一種常見的 react 模式, 好比官方的 Context API 和 react-spring 動畫庫. 目的高階組件差很少: 都是爲了分離關注點, 對組件的邏輯進行復用; 在使用和實現上比高階組件要簡單, 在某些場景能夠取代高階組件

    React 並無限定任何 props 的類型, 因此 props 也能夠是函數形式. 當 props 爲函數時, 父組件能夠經過函數參數給子組件傳遞一些數據進行動態渲染

    <FunctionAsChild>{() => <div>Hello,World!</div>}</FunctionAsChild>    
    複製代碼
    • 把特定行爲或功能封裝成一個組件,提供給其餘組件使用讓其餘組件擁有這樣的能力

      • 流程:封裝行爲或者功能成一個組件==》根據業務props(函數)事件(傳參)==》最後調用行爲組件

        const Bar = ({ title }) => (<p>{title}</p>);
        
             //接收函數式(title) => <Bar title={title} />的props==><Bar title="我是一個state的屬性" />
             //最後返回<p>我是一個state的屬性</p>---->實現的就是顯示<p>title</p>
               class Foo extends React.Component {
                 constructor(props) {
                   super(props);
                   this.state = { title: '我是一個state的屬性' };
                 }
                 render() {
                   const { render } = this.props;
                   const { title } = this.state;
               
               return (
                 <div>
                   {render(title)}
                 </div>
               )
             }
           }
        
           class App extends React.Component {
             render() {
               return (
                 <div>
                   <h2>這是一個示例組件</h2>
                   <Foo render={(title) => <Bar title={title} />} />
                 </div>
               );
             }
           }
           ReactDOM.render(<App />, document.getElementById('app'))
        複製代碼

在沒有hooks以前高階組件與props render是倆大利器

Web通訊中傳統輪詢、長輪詢和WebSocket簡介

Suspense

Suspense容許組件在渲染以前「等待」某些東西。今天,Suspense只支持一個用例:動態加載組件React.lazy。未來,它將支持其餘用例,如數據獲取。

// Usage of Clock
        const Clock = React.lazy(() => {
          console.log("start importing Clock");
          return import("./Clock");
        });
複製代碼

這裏咱們使用了React.lazy, 這樣就能實現代碼的懶加載。 React.lazy 的參數是一個function, 返回的是一個promise. 這裏返回的是一個import 函數, webpack build 的時候, 看到這個東西, 就知道這是個分界點。 import 裏面的東西能夠打包到另一個包裏。

<Suspense fallback={<Loading />}>
          { showClock ? <Clock/> : null}
        </Suspense>
複製代碼

React.lazy採用必須調用動態的函數import()。這必須返回一個Promise解析爲具備default包含React組件的導出的模塊。

----》查看路由的懶加載--》在路由

React最新API---HOOKs【第三種狀態共享方案】(將來趨勢,Vue3.0也會出hooks)

React Hooks 要解決的問題是狀態共享,是繼 render-props 和 higher-order components 以後的第三種狀態共享方案,不會產生 JSX 嵌套地獄問題

這個狀態指的是狀態邏輯,因此稱爲狀態邏輯複用會更恰當,由於只共享數據處理邏輯,不會共享數據自己。

先說說內部提供的hooks

  • useState:初始化state, setState可以修改值

    const [state, setState] = useState(initialState);
    複製代碼
  • 示例

    const [page, setPage] = useState(1); //初始化定義頁碼爲1
    
       setPage(2);
    複製代碼
  • useEffect:componentDidMount,componentDidUpdate,和 componentWillUnmount結合

    • 示例(用好驅動依賴)

      useEffect(() => {
           //componentDidMount
            //初始化位置列表
            resetAnimate();
            return () => {
                //componentWillUnmount
                // 清除訂閱
                subscription.unsubscribe();
              };
              //componentDidUpdate
          }, [props.ListData]); //返回數據變更做爲驅動依賴
      複製代碼
  • useContext:前面提到的React 新的 Context API,這個 API 不是完美的,在多個 Context 嵌套的時候尤爲麻煩。

    好比,一段 JSX 若是既依賴於 ThemeContext 又依賴於 LanguageContext,那麼按照 React Context API 應該這麼寫:

    <ThemeContext.Consumer>
           {
               theme => (
                   <LanguageContext.Cosumer>
                       language => {
                           //可使用theme和lanugage了
                       }
                   </LanguageContext.Cosumer>
               )
           }
       </ThemeContext.Consumer>
    複製代碼

    由於 Context API 要用 render props,因此用兩個 Context 就要用兩次 render props,也就用了兩個函數嵌套,這樣的縮格看起來也的確過度了一點點。

    使用 Hooks 的 useContext,上面的代碼能夠縮略爲下面這樣:

    const theme = useContext(ThemeContext);
       const language = useContext(LanguageContext);
       // 這裏就能夠用theme和language了
    複製代碼

    也不完美-----組件總會在 context 值變化時從新渲染,若是context裏只是size變了,也會形成從新渲染(shouldComponentUpdate)

  • 額外的 Hook

    • useReducer

      const [state, dispatch] = useReducer(reducer, initialArg, init);

      • 示例:React Hooks實現異步請求實例—useReducer、useContext和useEffect代替Redux方案

        const initialState = {count: 0};
        
            function reducer(state, action) {
              switch (action.type) {
                case 'increment':
                  return {count: state.count + 1};
                case 'decrement':
                  return {count: state.count - 1};
                default:
                  throw new Error();
              }
            }
        
            function Counter() {
              const [state, dispatch] = useReducer(reducer, initialState);
              return (
                <>
                  Count: {state.count}
                  <button onClick={() => dispatch({type: 'increment'})}>+</button>
                  <button onClick={() => dispatch({type: 'decrement'})}>-</button>
                </>
              );
            }
        複製代碼
      • useLayoutEffect

        其函數簽名與 useEffect 相同,但它會在全部的 DOM 變動以後同步調用 effect。可使用它來讀取 DOM 佈局並同步觸發重渲染。在瀏覽器執行繪製以前,useLayoutEffect 內部的更新計劃將被同步刷新。

      • useRef

        useRef 返回一個可變的 ref 對象,其 .current 屬性被初始化爲傳入的參數(initialValue)。返回的 ref 對象在組件的整個生命週期內保持不變

        function TextInputWithFocusButton() {
               const inputEl = useRef(null);
               const onButtonClick = () => {
                 // `current` 指向已掛載到 DOM 上的文本輸入元素
                 inputEl.current.focus();
               };
               return (
                 <>
                   <input ref={inputEl} type="text" />
                   <button onClick={onButtonClick}>Focus the input</button>
                 </>
               );
             }
        複製代碼

    。。。。。等等

很長一段時間,高階組件和 render props 是組件之間共享邏輯的兩個武器,但如同我前面章節介紹的那樣,這兩個武器都不是十全十美的,如今 Hooks 的出現,也預示着高階組件和 render props 可能要被逐步取代。

在 Hooks 興起以後,共享代碼之間邏輯會用函數形式,並且這些函數會以 use- 前綴爲約定,重用這些邏輯的方式,就是在函數形式組件中調用這些 useXXX 函數。

一篇看懂 React Hooks /// 2019年了,整理了N個實用案例幫你快速遷移到React Hooks(收藏慢慢看系列) /// React Hooks全面理解教程

官方自定義hooks介紹

hooks常見問題

DvaJS介紹使用

Dva+Antd-mobile項目實踐

相關文章
相關標籤/搜索