React总结来说(通信/redux/路由/钩子函数/三大解决逻辑共享方案(组件))

1,589 阅读31分钟

最全的(前端知识)汇总,以便自身复习

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

由于篇幅过于长,知识体系过于庞杂,请谨慎学习, 转述的我都有放置链接(难理解的也放了demo测试或者图)

技术规范(基础了解,大佬尽可跳过)

React

通信问题

父-->子

父组件传递到子组件,符合了react的单向数据流理念,自上而下的传递props

  • 父组件

      class Parent extends Component {
          
          ......省略
          
          render(){
              let {value}=this.state;
             return(  
                   <Child value={value} ></Child>
               )
          }
      }
    
  • 子组件

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

子-->父

依赖props的引用,通过父组件传递给子组件的回调函数【父组件属性为函数】来实现

  • 父组件

         class Parent extends Component {
    
              ......省略
            setValue = value => {
              this.setState({
                value,
              })
        }
    
        render() {
          return (
            <div>
            //父组件传递给子组件setValue的方法【回调函数 】
              <Child setValue={this.setValue} />
            </div>
          );
        }
      }
    
  • 子组件

       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【容器或者说父组件】

    • Container(容器)

        // 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项目实践