react 汇总

  • 安装
    • cnpm install create-react-app -g 脚手架
    • create-react-app reactdom 初始化项目

基础用法

1<Parent>xxx</Parent>
子组件 通过this.props.children 获取xxx

2<Parent number={2}>xxx</Parent>{}传递的就是数值

3let obj = {num:1} 组件传值
<Parent {...obj} n='2'></Parent>

4<Counter><p>123</p></Counter>
    获取组件里面的子元素 => this.props.children
    渲染 一般不用map 因为 里面可能有一个 可能都有多个最终得到的结构都不一样
    官方有个方法 React.Children.map 第一个参数是this.props.children 第二个是要渲染的内容
    {React.Children.map(this.props.children,item=>{
        return <li>{item}</li>     
    })}

51、父传子 靠属性
  2、平级   靠公共的父组件
  3、跨级(祖孙) 靠Context api   祖传子-直接传   子传祖-用回调

6、Context api用法
    创建一个文件
    import React from 'react'
    let {Provider,Consumer} = React.createContext();
    export{Provider,Consumer}

    父级提供 value是固定的
        <Provider value={{r:传递的内容}}> 父级组件 </Provider>
    子级
        <Consumer>{({r})=>{
            return(<div>
                    <button onClick={()=>{
                        console.log('r',r)
                    }}>按键
                    </button>
                </div>)
        }}</Consumer>

7、ref
    声明
    input = React.createRef()
    绑定
    <input type='text' ref={this.input}/>
    获取
    console.log(this.input)

8、函数里面的别名
  let a  =  {s:"123"}
  function A({s:q}){
    console.log(q) //此时q就是s的别名 为123
  }
  A(a)

9、获取当前元素的节点
    <input type="text" ref={input=>this.content=input}/>
    console.log(this.content)

生命周期

开始阶段
  componentWillMount 组件将要渲染
  componentDidMount  组件渲染完成
  render             开始渲染
      只要调用setState  无论数据是否变化 都会调用render方法
变化
  shouldComponentUpdate(nextProps,nextState)
    //优化在这里做,返回ture 执行render() false不执行 

  componentDidUpdate  组件更新完成
  componentWillUpdate 组件将要更新

  componentWillReceiveProps 组件接受到了新的属性  (第一次不触发)
  componentWillUnmount  组件将要销毁 

  setState 在生命周期 开始阶段 和 将要接收新的属性调用 其他调用会无限循环

新的              
  getSnapshotBeforeUpdate(prevProps,prevState) 组件将要更新(把以前的替换了)
  //获取更新前的快照,必须有一个返回值(更新之前的属性,状态)会在componentDidUpdate第三个参数获取到
  static getDerivedStateFromProps(nextProps, prevState) 接受新的属性 (把以前的替换了)
  //第一次会触发,返回的是一个新的状态,没有显示调用setstate  返回的值直接盖上去
  //生命周期都是同步,获取数据 放在组件更新完成、将要更新发送都可以  反正是要渲染2次的     

PropTypes验证

  • import PropTypes from 'prop-types'
  //部分
  export default class Types extends Component{
    static a = 'aaaaaaaaas'
    
    // 名字是死的  给类增加静态属性 //es7 才有的
    // defaultProps 和 父组件传递进来的 一样 都是this.props下的
    static defaultProps = {
        name:'1231'
    }
    //校验属性  只要是 this.props.下能获取到 都可以校验 (上面的name  也是)
    static propTypes = {
      age:PropTypes.string,
      name:PropTypes.string,
      sex:PropTypes.oneOf(['男','女']),
      fan:PropTypes.shape({
          a:PropTypes.number,
          b:PropTypes.string,
      }),
      //数组
      arr:PropTypes.arrayOf(PropTypes.string),
      //自定义校验
      number2(props,propName){
          //第一个是所有的this.props , propName指的就是number2
          if(props[propName]>100000){
              throw new Error('收益太低')
          }
      }
    }
    render(){
        // console.log('123',this.props)
        // console.log('123',Types.a)
        return (<div>
              types
        </div>)
        }
}

setState 异步渲染问题

  • setState用法
  setState用法
    第一个参数是赋值 第二个参数是回调
    this.setState({number:this.state.number},()=>{
      this.setState(.......)
    })
    一般加异步是为了一个个执行 但都是后面覆盖前面
    用这个回调 也可以一个个执行 , 但这个是嵌套
      也可以,prevState是上次的结果
      this.setState((prevState)=>({number:prevState.number+1}))
      this.setState((prevState)=>({number:prevState.number+3}))

  add1 = ()=>{
      // 只会渲染一次
      this.setState({
          number:this.state.number+1
      })
      this.setState({
          number:this.state.number+3
      })
      this.setState({
          number:this.state.number+2
      })
  }
  add2 = ()=>{
    // 加异步  就会渲染多次
    setTimeout(()=>{
        this.setState({
            number:this.state.number+1
        })
        this.setState({
            number:this.state.number+3
        })
        this.setState({
            number:this.state.number+2
        })
    },1000)
  }
  • setState原
  // 默认是批量更新的   setstate 异步更新原理
  let isBatchingUpdate = true
let transcation = (component)=>{
    component.state = component.pendingState
    component.render();
    isBatchingUpdate = false
}

class My{
  constructor(){
      this.state = {number:0};//自己的状态
      this.pendingState = {...this.state}
  }
  setState(obj){
      if(isBatchingUpdate){
          //批量更新 这里直走一次  也就是后面覆盖前面
          // console.log('批量更新',obj)
          this.pendingState = {...this.pendingState,...obj}
      }else{
          //异步更新 这里走完 一次 在
          // console.log('异步更新',obj)
          this.pendingState = {...this.pendingState,...obj}
          // 每次都会回调自己
          transcation(this)
      }
  }
  update(){
    // frist 如果是异步先走 transcation  那么isBatchingUpdate为false setState就会走else里面
    //       如果是同步 就是直接调用 setState 走if(ture)

      setTimeout(()=>{
          this.setState({number:this.state.number + 1})
          this.setState({number:this.state.number + 3})
          this.setState({number:this.state.number + 2})
      },0)
      transcation (this)
      
  }
  render(){
      // console.log(this.state.number)
  }
}

  let my = new My()
  my.update()

路由

  • 分hash #和 history h5api (刷新的时候页面不存在,一定会出现404的问题)
  • h5api 就是 history.pushState('xx',null,'./a')
  • history 只能通过popstate监听浏览器的前进和后退
  • npm install react-router-dom
  • BrowserRouter 和 HashRouter两种
  import {HashRouter as Router,Route,Switch,Redirect,Link,NavLink} from 'react-router-dom'
  基本用法
    Router 可以切换 hash 和	history  要包裹在最外面
    Route  用来声明路由的
      <Route path='/'  exact={true} component={Home}/>  
					exact={true} 是完全匹配 若是下面有二级路由不能加 
					通过Route组件渲染出来的页面 有三个属性 history,lication,match 

    Redirect 重定向  当没有匹配到路由时候的跳转
      <Redirect to='/' />
    
    Switch 匹配到一个后就不会在匹配了
      <Switch>
        里面包裹 Route组件 和 Redirect组件
      </Switch>
      
    Link 标签用来跳转的 (一般对Link和NavLink第二次包装的时候 
      可以在他们前面都Route 标签 可以获取history,lication,match属性)
    <Link to='/'>首页</Link>

    NavLink 跟Link一样的
			只是当路由是当他被激活的时候  会加一个class='active'
        注意 to='/' 要加一个  exact={true}   不然点击别的时候 他也会加
          二级路由 不能写 exact
                
        path 和 to ="/" 最前面一定要加/
                 
			JS跳转路由	
			 	this.props.history.push('/user/list')
    
  路由传值
    <Route path='/user/:id'  exact={true} component={Home}/>  
		<link to={'/user/'+id}>跳转</Link>

  路由的高阶组件
    将Route 用组件包装做判断而已
      1、Route 默认情况下 直接 compoment={B} 之间渲染B组件   
      2、包装 <A><A> => A组件返回一个包装后的Route 组件, 可以在里面做拦截等逻辑
  

render component children区别

  • render和children 都是一个函数 他返回的结果被进行渲染 component返回一个组件
  • children 不管路径没有没匹配到 都会渲染 如果是当前路径 则props.match 为ture
    • 一般对NavLink做 二次处理 如果跳转的是当前 可以把active放到上级
  • render 返回的是一个组件(函数)
    • 一般对Route 进行二次处理 如果登陆就正常返回 否则 拦截等操作

react-redux用法 && 原理

  • 创建一个store

  • actions 存放动作 => 导出的是一个对象 对象里面有操作的方法 方法返回对象 获得的对象传入reducers的第二个参数

  • reducers 存放管理员 => 就是一个函数接受(state,aciton) 返回一个对象 修改store的数据

  • index.js 导出store 将reducers传入redux里面的createStore中

  • types.js 存放操作类型 操作的类型 避免写错 所以集中存放

    • redux 在创建的store的时候 将reducer(管理员放进去) 默认是执行一次dispatch state会接受默认值
    • 在组件内是通过调用store.dispatch 修改state里面的东西 store.getState获取state里面的值
  • react-redux用法

  • 入口文件引入store

/**   
 * react-redux 在父级提供store 这样在每个组件中就不用引入store
 * 提供了 connnect和Provider方法
 */
import { Provider } from "react-redux";
import store from "./store";
ReactDom.render(
  <Provider store={store}>
  <>
    <Counter />
  </>
  </Provider>,
  window.root
);
  • 下面的文件 引入connect
  • connect 连接有两种写法
import actions from '../store/actions/Counter'
// import {bindActionCreators} from 'redux';
import {connect} from 'react-redux'

class Counter extends Component {
  state = {
    number: ''
  };
  render() {
    console.log(this.props)
    return (
      <>
        <p>{this.props.number}</p>
        <div onClick={() => this.props.del(3)}>点击减少</div>
        <div onClick={() => this.props.add(3)}>点击增加</div>
      </>
    );
  }
}
//connect 方法执行2次后 返回的是一个组件
//第二个参数是原来的组件 会把redux中的状态映射到这个组件上
// 11 、connect 第一个参数
// let mapStateToProps = (state) => { //store.state
//   console.log('state',state)
//   return {
//     number : state.counter.number
//   }
// }
// 12、 简化1的写法
let mapStateToProps = (state) => ({...state.counter})
// 21 、connect 第二个参数
let mapDispatchToProps = (dispatch) => { //store.dispatch
  return{
    // store.dispatch({ type: "ADD", count: 3 })
    add:(num)=>dispatch(actions.add(3)),
    del:(num)=>dispatch(actions.del(3))
  }
}
export default connect(mapStateToProps,mapDispatchToProps)(Counter)

// 22 、简化2写法
// dispatch原本就是connect传递进来的  bindActionCreators 是redux方法接收 actions和dispatch 会包装成 1 返回的那种形式 

// function bindActionCreators(actions,dispatch){
//   let obj = {}
//   for(let key in actions){
//     obj[key] = (...args)=>dispatch(actions[key](...args))
//   }
//   return obj;
// }
// export default connect(mapStateToProps,(dispatch)=>bindActionCreators(actions,dispatch))(Counter)

// 3、如果connect 第一次执行的函数,如果第二个参数是对象类型,会自动内部调用bindActionCreators来实现
// export default connect(mapStateToProps,actions)(Counter)

中间件

logger

import {createStore} from '../redux';
import reducers from './reducers';
let store = createStore(reducers)
let dispath = store.dispatch;//缓存老的原始派发方法
sotre.dispath = function(action){
  console.log('老状态',store.getState())
  dispath(action);
  console.log('新状态',store.getState())
}
export default store

  • 原理
  • 创建react-redux/connect.js
import Context from './context'
import React, { Component } from 'react'
import {bindActionCreators} from 'redux';

let connect =(mapS,mapD)=>(Component)=>{
  return ()=>{
    class Proxy extends React.Component{
      state = mapS(this.props.store.getState())
      componentDidMount() {
        this.unsub = this.props.store.subscribe(()=>{
          this.setState(mapS(this.props.store.getState()))
        })
      }
      componentWillUnmount(){
        this.unsub()
      }
      render(){
        let  d;
        if(typeof mapD === 'object'){
          d = bindActionCreators(mapD,this.props.store.dispatch)
        }else{
          d = mapD(this.props.store.dispatch)
        }
        return <Component {...this.state} {...d} ></Component> 
      }
    }
  return (<Context.Consumer>
    {({store})=>{
      return <Proxy store={store}></Proxy>
    }}
    </Context.Consumer>)
}
}
export default connect
  • 创建react-redux/context.js
import react from 'react'

let Context = react.createContext()

export default Context
  • 创建react-redux/index.js
import Provider from './provider'
import connect from './connect'
export {Provider,connect}
  • 创建react-redux/provider.js
import context from './context'
import React, { Component } from 'react'

export default class Provider extends React.Component{
  render(){
    return (
      <context.Provider value={{store:this.props.store}}>
      {this.props.children}
      </context.Provider>
    )
  }
} 

couter&&todoList 例子

  • 目录
  • src
    • components
      • counter.js
      • List.js
    • store
      • actions
        • counter.js
        • list.js
      • reducers
        • counter.js
        • list.js
        • index.js
      • index.js
      • types.js
    • index.js
    • saga.js
  • components/counter.js
import React from 'react'
import {connect} from 'react-redux'
import actions from '../store/actions/counter'
class Counter extends React.Component{
  render(){
    return (
      <div>
        <div>计数</div>
        <div>{this.props.count}</div>
        <button onClick={this.props.add}>添加</button>
        <button onClick={this.props.asyncAdd}>async 添加</button>
        <button onClick={this.props.sagaAdd}>saga 添加</button>
        
        <button onClick={this.props.del}>减少</button>
        <button onClick={this.props.asyncDel}>async 减少</button>
      </div>
    )
  }
}
let  mapStateToProps = (state)=>{
  return{
    count : state.counter.count
  }
}

let mapDispatchToProps = (dispatch)=>{
  //store.dispatch
  return{
    add:()=>{
      dispatch(actions.add(1))
    },
    asyncAdd:()=>{
        return dispatch(actions.add(1))
    },
    sagaAdd:()=>{
      // saga 异步
      dispatch(actions.sagaAdd(1))
    },
    del:()=>{
      dispatch(actions.del(1))
    },
    asyncDel:()=>{
      // thunk 异步
      setTimeout(()=>{
        return dispatch(actions.del(1))
      },1000)
    },
  }
}

export default connect(mapStateToProps,mapDispatchToProps)(Counter)
  • components/List.js
import React from 'react'
import {connect} from 'react-redux'
import actions from '../store/actions/list'
class List extends React.Component{
  render(){
    return (
      <div>
        <div>ToDo List</div>
        {this.props.count.map((item,index)=>{
          return (
            <div key={index}>
              {item}
            </div>
          )
        })}
        <button onClick={this.props.add}>添加</button>
      </div>
    )
  }
}
let  mapStateToProps = (state)=>{
  //store.getState()
  return{
    count : state.list.data
  }

}
let mapDispatchToProps = (dispatch)=>{
  //store.dispatch
  return{
    add:()=>{
      dispatch(actions.tolist(1))
    }
  }
}
export default connect(mapStateToProps,mapDispatchToProps)(List)
  • store/actions/counter.js
import * as Types from '../types'
export default {
    add(n){
      return {type:Types.INCREMENT,count:n}
    },
    sagaAdd(n){
      return {type:Types.SAGA_ADD,count:n}
    },
    del(n){
      return {type:Types.DECREMENT,count:n}
    },
}
  • store/actions/counter.js
import * as Types from '../types'
export default {
  tolist(data){
    return {type:Types.TOLIST,data}
  }
}
  • store/reducers/counter.js
import * as Tpyes from '../types'
export default function counter (state={count:0},actions) {
  console.log('进来了',actions)
    switch(actions.type){
      case Tpyes.INCREMENT:
        return {count:state.count+actions.count}
      case Tpyes.DECREMENT:
        return {count:state.count-actions.count}
    }
    return state
}
  • store/reducers/list.js
import * as Types from '../types'
export default function list (state={data:[]},actions) {
  console.log(actions)
    switch(actions.type){
      case Types.TOLIST:
      console.log(actions)
      return {data:[...state.data,actions.data]}
    }
    return state
}
  • store/reducers/index.js
import {combineReducers} from 'redux';
import counter  from './counter'
import list  from './list'
export default combineReducers({
  counter,
  list
})
  • store/index.js
import {createStore,applyMiddleware} from 'redux';
import reducer from './reducers';
import thunk from 'redux-thunk';
import createSagaMiddleware  from 'redux-saga'
import rootSaga from '../saga'
let sageMiddleware = createSagaMiddleware()
// sageMiddleware 是用来拦截对saga中间件请求的
let store = createStore(reducer,applyMiddleware(sageMiddleware));
sageMiddleware.run(rootSaga)
export default store
  • store/types.js
export const INCREMENT = 'INCREMENT'
export const TOLIST = 'TOLIST'
export const DECREMENT = 'DECREMENT'
export const SAGA_ADD = 'SAGA_ADD'
  • index.js
import React from 'react';
import ReactDOM from 'react-dom';
import Counter from './components/Counter';
import List from './components/List';
import {Provider} from 'react-redux'
import store from './store'
ReactDOM.render(
  <Provider store={store}>
    <Counter />
    <List />
  </Provider>
  , document.getElementById('root'));
  • saga.js
import {takeEvery,put} from 'redux-saga/effects'
import * as Types from './store/types'

const delay = ms =>new Promise((res,rej)=>{
  setTimeout(res,ms)
})

// 这是saga的唯一入口
export default function * rootSaga(){
  //拦截或者监听SAGA_ADD动作,然后执行对应的workerSaga
  yield takeEvery(Types.SAGA_ADD,minus);
}

export function * minus() {
  //当yield一个promise的时候,程序不会立刻执行 等待时间 在执行
  yield delay(1000);//产出了一个promise
  //put相当于dispatch(action)
  yield put({type:Types.INCREMENT,count:1})
}

redux 源码

function CreateStore(reducer) {
  let state;
  let getState = () => state;
  let listeners = [];
  let dispatch = (action)=>{
    state = reducer(state,action)
    listeners.forEach(fn=>fn())
  }
  dispatch({type:'@INIT'})
  let subscribe = (fn)=>{
    listeners.push(fn)
    return ()=>{
      listeners = listeners.filter(l=>l!=fn)
    }
  }
  return {
    getState,
    dispatch,
    subscribe
  }
}

/*
 bindActionCreators 按照下面仿制的
let mapDispatchToProps = (dispatch) => { //store.dispatch
  return{
    del:(num)=>dispatch(actions.del(3))
  }
}
export default connect(mapStateToProps,mapDispatchToProps)(Counter)
*/
function bindActionCreators(actions,dispatch){
  let obj = {}
  for(let key in actions){
    obj[key] = (...args)=>dispatch(actions[key](...args))
  }
  return obj;
}

// combineReducers 合并reducer 用法
// import counter from './counter'
// import subtraction from './subtraction'
// import { combineReducers } from 'redux'
// export default combineReducers({counter,subtraction})

// 原理 
// reducers是单个reducer组成的
function combineReducers(reducers){
  // 这里的state是一个总的
  return function(state={},action){
    let obj = {}
    for(let key in reducers){
      //为什么state加key 因为这个要给用户点的 
      obj[key] = reducers[key](state[key],action)
    } 
   return obj
  }
}


// compose 原理
function add1(str){
  return '1' + str
}
function add2(str){
  return '2' + str
}
function add3(str){
  return '3' + str
}
// compose作用将参数传递给左边第一个 他的返回值在传递给左边的 一直下去
let rs = compose(add1,add2,add3)('zzz')
console.log(rs)// 123zzz

function compose(...fns){
  if(fns.length === 0) return args=>args;
  if(fns.length === 1) return fns[0];
  // fns.reduce((add1,add2)=>('zzz') => a(b('zzz')))
  // ...args 就是指 'zzz'
  return fns.reduce((a,b)=>(...args) => a(b(...args)))
}


// logger中间件 {getState,dispatch},dispatch,action 都是默认的 根据applyMiddleware 来的
function logger({getState,dispatch}){
  return function(next){
    return function(action){
      console.log('老状态',getState())
      next(action);
      console.log('老状态',getState())
    }
  }
}
//或者
let logger = store => dispatch => action=>{}

// 中间件的用法
 let store =  applyMiddleWare(logger)(createStore)(reducer);

// applyMiddleware中间件写法 模仿可以跟着中间件的用来的写,最终就是要返回一个store  
// 第一个参数就是中间件
function applyMiddleware(...middlewares){
  return function(createStore){
    return function(reducer){
      let store = createStore(reducer)
      //1
      let dispatch = ()=>{throw Error('xxx')}
      let middleswareApi = {
        getState:store.getState,
        // 这里的dispatch 为毛要加后面的  因为下面dispatch(3) 重新赋值后导致dispatch(1)变化 如果不加后面的 此处的dispatch始终等于 ()=>{throw Error('xxx')}
        dispatch:(...args)=>dispatch(...args)
      }
      const chain =  middlewares.map(middleware=>middleware(middleswareApi))
      // 3
      dispatch = compose(...chain)(store.dispatch)
      return {
        ...store,
        dispatch
      }
    }
  }
}


export { CreateStore, bindActionCreators, combineReducers, applyMiddleware};

redux-thunk 原理

function createThunkMiddleware() {
  return ({dispatch,getState })=>next=>action{
    if(typeof action == 'function'){
      // 传递过来的action是一个 函数
      return action(dispatch,getState )
    }else{
      return next(action)
    }
  }
}
const thunk = createThunkMiddleware();
thunk.withExtraArgument = createThunkMiddleware;

export default thunk;

redux-saga 原理

  • redux-saga是一个中间件

  • 在reducers中所有的操作都是同步,reducers是纯函数

  • sage采用Generator函数用来yield Effects

  • Effects是一个简单的对象,该对象包含了一些给middleware解释执行的信息

  • 可以通过使用effects API 如fork,call,take,put,cancel等来创建

  • 步骤

    • 1、rootSaga 入口saga 是用来组织和调用别的saga的
    • 2、watcher saga监听被dispatch的actions,当接收到action或者知道其被触发时,调用worker执行任务
    • 3、worker saga做实际的工作,如调用api进行异步请求
  • 用法

    • 在store入口使用
    • 创建一个saga文件
    • effect有很多方法(takeEvery,put,take,call,all,delay,cps,select)
      • all 类似promise.all 可以放多个
      • take 只监听(动作)一次 会阻塞 执行他会有一个返回值 就是监听的action,'*'监听所有(通配符)
      • put 派发动作(就时一个dispatch) take监听 put派发 take 和 put 不能传递一样的类型 会无线循环
      • takeEvery(action,Generator) takeEvery会监听action 去执行Generator 监听每一次 不会阻塞 他就等于take 和 put 都执行一次
      • delay 就是一个slepp传递一个时间
      • cps 接收一个普通的方法
      • call 处理异步 接收一个promise的方法
      • select 执行返回仓库的状态(state)
  // store/index 文件
  import createSagaMiddleware  from 'redux-saga'
  import rootSaga from '../saga'
  let sageMiddleware = createSagaMiddleware()
  //sageMiddleware放到中间件里面
  let store = createStore(reducer,applyMiddleware(sageMiddleware));
  //运行
  sageMiddleware.run(rootSaga)

  // src/saga 文件
  import {takeEvery,put,take,call,all } from './redux-saga/effect'
  import * as Types from './store/types'
  
  // rootSaga 是核心的 导出函数
  export default function * rootSaga(){
    for(var i=0;i<3;i++){
      console.log(`第${i}次`)
      // take监听一次 会触发 subscribe  
      // 默认rootSaga会执行一次  也就是take会被
      // take 里面的type 要和 按键触发的 type 一样 才能触发 put
      yield take(Types.SAGA_ADD)
      // put 当点击的时候 才触发他
      yield put({type:Types.INCREMENT,count:1})
    }
    console.log('结束了。。。')
  }
  // 或者
  // 定时器
  // const delay = ms =>new Promise((res,rej)=>{
  //   setTimeout(res,ms)
  // })
  // export default function * rootSaga(){
  //   //拦截或者监听SAGA_ADD动作,然后执行对应的workerSaga
  //   yield takeEvery(Types.SAGA_ADD,minus);
  // }

  // export function * minus() {
  //   //当yield一个promise的时候,程序不会立刻执行 等待时间 在执行
  //   yield delay(1000);//产出了一个promise
  //   //put相当于dispatch(action)
  //   yield put({type:Types.INCREMENT,count:1})
  // }
// call 一般异步用
  // const delay = ms =>new Promise((res,rej)=>{
  //   // setTimeout(res,ms)
  //   setTimeout(()=>{
  //       res('--',new Date())
  //   },ms);
  // })

  // export default function * rootSaga(){
  // let date = yield call(delay,1000)
  // }

// all 全部的意思 promise.all

  // function* helloSaga1(){
  //   console.log('helloSaga1')
  // }  
  // function* helloSaga2(){
  //   console.log('helloSaga2')
  // }  
  // export default function * rootSaga(){
  //   yield all([helloSaga1(),helloSaga2()]);
  //   console.log('the end')
  // }

  再派发的时候 只需要添加 type 对应的类型  每次触发 redux-saga就会拦截处理
   take(type)里面是拦截的类型
   put(actions) 里面真正dispatch的内容
  • saga原理
    • 主要依靠co库 和 发布订阅模式
    • rootSage放到run(rootSage)执行了
    • createChannel 函数就是一个发布订阅
    • take就是订阅 将每次接受的类型存储 => 触发 subscribe
    • takeEvery将得到的生成器 通过fork传递进去 在开器子进程 run(生成器) 最后子进程去publish
    • channel.publish(action) 就是拦截中间件的 检查传递进来的type和take里面的type是否一致
      • 若相同 执行listener(action) 也就是take传递的第二个参数 就是回调next() => put
  • redux-saga/index
export default function createSagaMiddleware(){

  //发布订阅
  function createChannel(){
    let events = {};
    function subscribe(actionType,listener){
     console.log('actionType',actionType)
      events[actionType] = listener
    }
    function publish(action){
     let listener = events[action.type]
     console.log('actionType',action)
      if(listener){
        // 默认只执行一次 所以要删除
        delete events[action]
        //  listener(action) 就是执行中间件的next(action)
        listener(action)
      }
    }
    return {subscribe,publish}
  }

  let channel = createChannel();

  function times(done,total){
    let count = 0;
    return function(){
      if(++count == total){
        done()
      }
    }
  }

  function sagaMiddleware(stroe) {
    function run(generator,finish) {
      // 执行生成器,得到迭代器
      let it =  typeof generator == 'function' ? generator():generator;

      function next(action){
        // value:effect => 将value改名为effect
        let {value:effect,done} = it.next(action);
        console.log('默认执行一次,也就是执行take 发布一下而')
        if(!done){
          if(typeof effect[Symbol.iterator] == 'function'){
              run(effect);
              next();
          }else if(effect.then){
            effect.then(next);
          }else{
            switch(effect.type){
                case 'take'://订阅某个动作类型
                    channel.subscribe(effect.actionType,next);
                    break;
                case 'put':
                    stroe.dispatch(effect.action)
                    next();
                    break;
                case 'fork':
                    // 接受到的是一个生成器 需要传给run处理
                    run(effect.task);
                    next();
                    break;
                case 'call':
                    effect.fn(...effect.args)
                    .then(next);
                    break;
                case 'all':
                    let final = times(next,effect.fns.length) 
                    effect.fns.forEach(fn=>run(fn,final));
                default:
                    break;
            }
        }
      }else{
        //全部完成后
          console.log('完成')
          finish && finish();
      }
      next();
    }
    sagaMiddleware.run = run;
    return function (next) {
      return function (action) {// store.dispatch 就等于执行最里面的函数
        channel.publish(action)//派发action
        next(action)
      }
    }
  }
  return sagaMiddleware
}
  • redux-saga/effect.js
export function  take(actionType){
    return {
      type:'take',
      actionType
    }
  }
export function  put(action){
    return {
      type:'put',
      action
    }
  }
export function fork(task){
  return {
    type:'fork',
    task
  }
}
/**
 * 1、不能阻塞当前的generator函数
 *  开启一个新的线程 就相当于单独重新run
 * fork   
 */
export function* takeEvery(actionType,task){
  yield fork(function *(){
    while(true){
      yield take(actionType)
      yield task();
    }
  })
}

export function call(fn,...args){
  return{
    type:'call',
    fn,
    args
  }
}

export function all(fns){
  return {
    type:'all',
    fns
  }
}
  • src/saga
import {takeEvery,put,take,call,all } from './redux-saga/effects'
import * as Types from './store/types'

/**
 * take 会阻塞  take 和 put 不能传递一样的类型 会无线循环
 */
// export default function * rootSaga(){
//   for(var i=0;i<3;i++){
//     console.log(`第${i}次`)
//     // take监听一次 会触发 subscribe  
//     // 默认rootSaga会执行一次  也就是take会被
//     // take 里面的type 要和 按键触发的 type 一样 才能触发 put
//     yield take(Types.SAGA_ADD)
//     // put 当点击的时候 才触发他
//     yield add()
//   }
//   console.log('结束了。。。')
// }

/**
 * takeEvery 不会阻塞 && call
 * takeEvery 第三个参数 会传递给 第二个参数(生成器)
 * 第二个参数 生成器最后一个参数 默认接收到的是action
 */
// const delay = ms =>new Promise((res,rej)=>{
//   // setTimeout(res,ms)
//   setTimeout(()=>{
//       res('--',new Date())
//   },ms)
  
// })
// // 这是saga的唯一入口
// export default function * rootSaga(){
//   //拦截或者监听SAGA_ADD动作,然后执行对应的workerSaga
//   yield takeEvery(Types.SAGA_ADD,add);
// }

// export function * add(action) {
//   //当takeEvery 没有传值的时候 默认就是action 若传递值 最后一个 也是acrion

//   //当yield一个promise的时候,程序不会立刻执行 等待时间 在执行
//   // let rs =yield delay(1000);//产出了一个promise
//   // console.log('rs',rs)
//   // 一般异步用 call
//   let date = yield call(delay,1000)
//   //put相当于dispatch(action)
//   yield put({type:Types.INCREMENT,count:1})
// }

/**
 * all
 */

 function* logger(){
   console.log('加1.。')
 }
 function* loggerWatcher(){
   yield takeEvery(Types.SAGA_ADD,logger)
 }

 function* add(){
   yield put({type:Types.INCREMENT,count:1})
 }
 function* addWatcher(){
   yield takeEvery(Types.SAGA_ADD,add)
 }
 // all 全部的意思 promise.all
export default function * rootSaga(){
  //拦截或者监听SAGA_ADD动作,然后执行对应的workerSaga
  yield all([loggerWatcher(),addWatcher()]);
  console.log('the end')
}

typescript

  • Typescript是Javascript的超集,遵循最新的ES5/ES6规范。TypeScript扩展了Javascript语法

umi

  • UmiJS是一个类 Next.JS 的 react 开发框架。
  • 他基于一个约定,即 pages 目录下的文件即路由,而文件则导出 react 组件
  • umi 里约定目录下有 _layout.js 时会生成嵌套路由

hooks

  • Hook 是 React 16.8 的新增特性。它可以让你在不编写 class 的情况下使用 state 以及其他的 React 特性
  • 以前时用class 才能用state 现在用函数就可以直接操作state
  • 特点
    • 在可以用在函数组件中,并且可以在函数组件的多次渲染之间保持不变

useState (useState可以定义多个)

  • useState有两种写法 useState(0)和useState(()=>{})
  • let [state,setState] = useState(0)
import React,{useState} from 'react';
// 类
class Counter extends React.Component {
  constructor(props) {
      super(props);
      this.state = {
          number: 0
      };
  }
  render() {
      return (
          <div>
              <p>{this.state.number}</p>
              <button onClick={() => this.setState({ number: this.state.number + 1 })}>
                  +
        </button>
          </div>
      );
  }
}
// 函数
function Counter2(){
  // number,setNumber自己独有的
  const [number,setNumber] = useState(0);
  return (
      <>
          <p>{number}</p>
          <button onClick={()=>setNumber(number+1)}>+</button>
      </>
  )
}
export default Counter2;
  • 每次渲染都是独立的闭包
  • 函数式更新
  • 如果新的 state 需要通过使用先前的 state 计算得出,那么可以将函数传递给 setState。该函数将接收先前的 state,并返回一个更新后的值
function Counter2(){
  const [number,setNumber] = useState(0);
  function lazy(){
    setTimeout(()=>{
      // 每次获取的是当时的number 3秒后是调用的还是之前的number
      setNumber(number+1);
    },3000);
  }
  function lazyFunc(){
    setTimeout(()=>{
      // 每次获取的是最新的 number 3秒后是调用的是最新的 number
      setNumber(number=>number+1);
    },3000);
  }
  return (
      <>
          <p>{number}</p>
          <button onClick={()=>setNumber(number+1)}>+</button>
          <button onClick={lazy}>lazy+</button>
      </>
  )
}
  • 惰性初始 state
  • 如果你修改状态的时候,直接传的是老状态,则不重新渲染
function Counter3(){
  const [{name,number},setValue] = useState(()=>{
    return {name:'计数器',number:0};
  });
  return (
      <>
          <p>{name}:{number}</p>
          <button onClick={()=>setValue({number:number+1})}>+</button>
      </>
  )
}

memo&&PureComponent - useMemo&&usecallback

  • 父组件变化,子组件不变化,子组件默认会重新渲染,可以用下面2个方法(都是react里面的)包装后就不会重新渲染
  • memo针对函数 (子组件不接收值的情况下)
  • PureComponent 针对类
  • 为什么父组件重新渲染,子组件不变的情况下,子组件还需要重新渲染
    • 因为addClick 和 data 是父组件传递给子组件的,父组件有值变化整个组件会重新渲染导致addClick 和 data重新赋值
  • useMemo&&usecallback
    • 第二个参数要给,会监控他的值是否有变化
    • useMemo(针对变量) 会判断组件变化前后值是否有变化 没有变化就将上次的值传过去(默认情况下,重新生成的对象值不变但是引用地址变化了)
    • usecallback(针对方法) 原理同上
// 子
function SubCounter({onClick=()=>{},data={number:0}}){
  console.log('zi')
  return(
    <button onClick={onClick}>{data.number}</button>
  )
}
SubCounter = memo(SubCounter)
// 父
let oldData,oldAddClick;
function counter(props){
  console.log('fu')
  const [name,setName] = useState('计数器')
  const [number,setNumber] = useState('计数器')

  const addClick = ()=>{
    setNumber(number+1)
  }
  console.log('addClick=',addClick == oldAddClick) //引用地址不一样 返回false
  oldAddClick = addClick;
  const  data = {number} 
  console.log('data=',data == oldData) //引用地址不一样 返回false
  oldData = data
    return(
      <>
        <input type='text' value={name} onChange={(e)=>setName(e.target.value)} />
        <SubCounter />
      </>
    )
}
// useMemo&&usecallback 配合memo的 修改后 
function SubCounter({onClick=()=>{},data={number:0}}){
  console.log('zi')
  return(
    <button onClick={onClick}>{data.number}</button>
  )
}
SubCounter = memo(SubCounter);
let oldData,oldAddClick;
const App = function counter(props){
  console.log('fu')
  const [name,setName] = useState('计数器')
  const [number,setNumber] = useState('计数器')

  const addClick = useCallback(()=>{
    setNumber(number+1)
  },[number])
  console.log('addClick=',addClick == oldAddClick)
  oldAddClick = addClick;
  const  data = useMemo(()=>({number}),[number]) 
  console.log('data=',data == oldData)
  oldData = data
  return (<>
      <input type='text' value={name} onChange={(e)=>setName(e.target.value)} />
      <SubCounter onClick={addClick} data={data}/>
    </>
  ) 
}

useReducer

  • useState 的替代方案。它接收一个形如 (state, action) => newState 的 reducer,并返回当前的 state 以及与其配套的 dispatch 方法
  • 在某些场景下,useReducer 会比 useState 更适用,例如 state 逻辑较复杂且包含多个子值,或者下一个 state 依赖于之前的 state 等
// useReducer 用法
const [state, dispatch] = useReducer(reducer, initialArg, init);
// 
const initialState = 0;

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

useContext

  • 接收一个 context 对象(React.createContext 的返回值)并返回该 context 的当前值
  • 当前的 context 值由上层组件中距离当前组件最近的 <MyContext.Provider> 的 value prop 决定
  • 当组件上层最近的 <MyContext.Provider> 更新时,该 Hook 会触发重渲染,并使用最新传递给 MyContext provider 的 context value 值
  • useContext(MyContext) 相当于 class 组件中的 static contextType = MyContext 或者 <MyContext.Consumer>
  • useContext(MyContext) 只是让你能够读取 context 的值以及订阅 context 的变化。你仍然需要在上层组件树中使用 <MyContext.Provider> 来为下层组件提供 context
const CounterContext = React.createContext();

function reducer(state, action) {
switch (action.type) {
  case 'increment':
    return {number: state.number + 1};
  case 'decrement':
    return {number: state.number - 1};
  default:
    throw new Error();
}
}
function Counter(){
let {state,dispatch} = useContext(CounterContext);
return (
    <>
      <p>{state.number}</p>
      <button onClick={() => dispatch({type: 'increment'})}>+</button>
      <button onClick={() => dispatch({type: 'decrement'})}>-</button>
    </>
)
}
function App(){
  const [state, dispatch] = useReducer(reducer, {number:0});
  return (
      <CounterContext.Provider value={{state,dispatch}}>
          <Counter/>
      </CounterContext.Provider>
  )

}

effect

  • useEffect 就是一个 Effect Hook,给函数组件增加了操作副作用的能力。它跟 class 组件中的 componentDidMount、componentDidUpdate 和 componentWillUnmount 具有相同的用途,只不过被合并成了一个 API
  • 简单理解 每次渲染都会执行useEffect
  • 下面例子,如果没有返回清除定时器,那么每次渲染一次就多开一个定时器,返回值在下次useEffect生效前执行
  • 或者给useEffect第二个参数一个空数组,他会监视第二个参数是否有变化,空数组始终不会有变化,useEffect里面的函数就不会重新执行
function Counter(){
  const [number,setNumber] = useState(0);
  // 相当于componentDidMount 和 componentDidUpdate
  useEffect(() => {
     console.log('开启一个新的定时器')
     const $timer = setInterval(()=>{
      setNumber(number=>number+1);
     },1000);
      return ()=>{
        console.log('销毁老的定时器');
        clearInterval($timer);
     } 
  });
  //   useEffect(() => {
  //    console.log('开启一个新的定时器')
  //    const $timer = setInterval(()=>{
  //     setNumber(number=>number+1);
  //    },1000);
  // },[]);
  return (
      <>
          <p>{number}</p>
      </>
  )
}

useRef(函数里面用)&&createRef(类里面用)

  • useRef返回一个可变的ref对象,其.current属性被初始化为传入的参数
  • 返回的ref对象在组件的整个生命周期保持不变
import {useRef,createRef} from 'react';
function Child(){
  const inputRef1 = createRef()// {current:''} 每次获取的对象不是同一个 值是一样的
  const inputRef = useRef()// {current:''} 每次获取的对象是一个 值是一样的
  function getFocus(){
    inputRef.current.focus()
  }
  return (
    <>
      <input ref={inputRef}>
      <button onClick={getFocus}> 获取焦点</button>
    </>
  )
}

forwardRef

  • 将ref从父组件中转发到子组件中的dom元素上
  • 子组件接受props和ref作为参数
function Child(props,ref){
  return (
    <input type="text" ref={ref}/>
  )
}
Child = forwardRef(Child);
function Parent(){
  let [number,setNumber] = useState(0); 
  const inputRef = useRef();
  function getFocus(){
    inputRef.current.value = 'focus';
    inputRef.current.focus();
  }
  return (
      <>
        <Child ref={inputRef}/>
        <button onClick={()=>setNumber({number:number+1})}>+</button>
        <button onClick={getFocus}>获得焦点</button>
      </>
  )
}

useImperativeHandle

  • useImperativeHandle 可以让你在使用 ref 时自定义暴露给父组件的实例值
  • 在大多数情况下,应当避免使用 ref 这样的命令式代码。useImperativeHandle 应当与 forwardRef 一起使用
function Child(props,ref){
  const inputRef = useRef();
  useImperativeHandle(ref,()=>(
    {
      focus(){
        inputRef.current.focus();
      }
    }
  ));
  return (
    <input type="text" ref={inputRef}/>
  )
}
Child = forwardRef(Child);
function Parent(){
  let [number,setNumber] = useState(0); 
  const inputRef = useRef();
  function getFocus(){
    console.log(inputRef.current);
    inputRef.current.value = 'focus';
    inputRef.current.focus();
  }
  return (
      <>
        <Child ref={inputRef}/>
        <button onClick={()=>setNumber({number:number+1})}>+</button>
        <button onClick={getFocus}>获得焦点</button>
      </>
  )
}

useLayoutEffect

  • 其函数签名与 useEffect 相同,但它会在所有的 DOM 变更之后同步调用 effect
  • 可以使用它来读取 DOM 布局并同步触发重渲染
  • 在浏览器执行绘制之前useLayoutEffect内部的更新计划将被同步刷新
  • 尽可能使用标准的 useEffect 以避免阻塞视图更新
function LayoutEffect() {
    const [color, setColor] = useState('red');
    useLayoutEffect(() => {
        alert(color);
    });
    useEffect(() => {
        console.log('color', color);
    });
    return (
        <>
            <div id="myDiv" style={{ background: color }}>颜色</div>
            <button onClick={() => setColor('red')}></button>
            <button onClick={() => setColor('yellow')}></button>
            <button onClick={() => setColor('blue')}></button>
        </>
    );
}

自定义hooks

  • 只要说一个方法,方便名的前缀是use开头,并且在函数内使用hooks,那么他就是一个自定义的hooks
import React,{useEffect,useState} from 'react'
import ReactDOM from 'react-dom'
function useNumber(){
  let [number,setNumber] = useState(0)
  useEffect(() => {
    setInterval(()=>{
      setNumber(number=>number+1)
    },1000)
  }, [])
  return  [number,setNumber]
}

function Counter1(){
  let  [number,setNumber] = useNumber()
  return (
      <div>
        <button onClick={()=>setNumber(number+1)}>{number}</button>
      </div>
      )
}
function Counter2(){
  let  [number,setNumber] = useNumber()
  return( 
      <div>
        <button  onClick={()=>setNumber(number+1)}>{number}</button>
      </div>
  )
}
const App = ()=>(<>
  <Counter1/>
  <Counter2/>
</>)

app.router(()=><App />);

dva用法

  • 流程图
  • 脚手架
    • npm install dva-cli@next -g(1+ 的版本采用的umi)
    • dva new xx
    • cd xx
    • cnpm i styled-components -S
    • npm start
  • 自己搭建
    • create-react-app dva-app
    • cd dva-app
    • cnpm i dva -S
  • 使用dva三步
    • 1、定义模型
    • 2、定义路由
    • 3、app.start( '#root')开始把路由定义渲染到#root里
  • 基本使用
import React from 'react'
import dva,{connect} from 'dva'

// dva 其实就是一个函数
const = app = dva();

const delay = ms => new Promise(function(resolve){
  setTimeout(()=>{
    resolve()
  },ms)
})
const get = (url)=>{
  let rs = fetch(url).then(res=>res.json())
  return rs
}
// 定义模型
app.model({
  //里面的是 子状态
  namespace:'count',
  state:{number:0},
  reducers:{
    // key就是动作类型,当你向仓库派发add动作的时候,就会执行对应的reducer修改仓库中的状态d
    //相当于action type 参数是老状态 返回值是新状态
    add(state,{payload}){
      // 数据持久化
      let data = {number:state.number + payload}
      localStorage.setItem('number',JSON.stringify(data))
      return data
      },
    minus(state,{payload}){return {number:state.number - payload}}
  },
  effects:{
    //effect 里面放的是generator
    // 第一个参数里action动作对象 effects副作用 reudx-saga/effects
    *addAmount(action,{put,call}){
      // yield call(delay,1000);
      let rs =  yield call(get,'http://localhost:3001/amount');
      yield put({type:'add',payload:rs.data})
    }
  },
  // 订阅
  subscriptions:{
    setup({history,dispatch}){
      let todosStr = localStorage.getItem('todos')
      let data = todosStr?JSON.parse(todosStr):[]
      // Warning: dispatch: todos/load should not be prefixed with namespace todos
      // 在model时派发action,不需要加命名空间的前缀 默认就是, 可以省略
      // dispatch({type:'todos/load',payload:list})
      dispatch({type:'add',payload:data})
    }
  }
});

// connect里面传递action props就没有dispatch(2个方法不兼容)
// 默认都不传递  直接用dispatch type后面要配合匿名空间 
const App = connect(
  state=>state.count
)((props)=>(
  <div>
      <h2>{props.number}</h2>
      <button onClick={()=>props.dispatch({type:'count/addAmount'})}>+</button>
      <button onClick={()=>props.dispatch({type:'count/minusAmount'})}>-</button>
  </div>
))
app.router(()=><App/>);
app.start('#root')

  • dva 原理
    • dome 和原理 配合看
import React from 'react';
import dva,{connect} from './dva';
import {Router,Route} from './dva/router'
import {Link} from 'react-router-dom'
const app = dva();
const delay = ms => new Promise((resolve)=>{
  setTimeout(()=>{
    resolve()
  },ms)
})
app.model({
  namespace:'count',
  state:{number:0},
  reducers:{
    add(state){
      return {number:state.number+1}
    }
  },
  effects:{
    *asyncAdd({call,put},actions){
      yield call(delay,1000);
      console.log('jinali l ',actions)
      yield put({type:'count/add'})
    }
  }
});
const Count = connect(
  state => state.count
)(props=>( 
    <div>
      <button onClick={()=>props.history.goBack()}>返回</button>
         <p>{props.number}</p>
        <button onClick={()=>props.dispatch({type:'count/add'})}>+</button>
        <button onClick={()=>props.dispatch({type:'count/asyncAdd'})}>asyncAdd+</button>
    </div>
))
const Home = () =>(
  <div>
    <h3>Home</h3>
    <Link to='/count'>count</Link>
  </div>
) 

app.router(({history, app}) => (
  <Router history={history}> 
    <div>
      <Route exact path='/' component={Home} />
      <Route  path='/count' component={Count} />
    </div>
  </Router>
))
app.start('#root')
  • dva/router.js
import {Router,Route} from 'react-router';
export{
  Router,Route
}
  • dva/index.js
import React from 'react';
import {createStore,combineReducers,applyMiddleware} from 'redux';
import {Provider,connect} from 'react-redux';
import ReactDom from 'react-dom'
// import {takeEvery} from 'redux-saga/effects'
import * as effects from 'redux-saga/effects'
import createSagaMiddleware from 'redux-saga'
import { createHashHistory } from 'history'
// 普通导出 引入的时候需要结构
export {connect}
// 默认导出 引入直接用
export default function(){
  const _app={
    _store:null,//放所有的仓库
    _models:[],//这里面放着所有的模型
    model,//方法
    _route:null,//所有的路由配置
    router,//方法
    start,//方法
  }
  function model(m){
    //每次调用model 就将他存放到总的模型库里面
    _app._models.push(m)
  }
  function router(routerConfig){
    _app._router = routerConfig
  }
  const history = createHashHistory()
  function start(root){
    const App = _app._router({history,app:_app});
    // 每一个模型 都有namespace 都是状态树中的子属性, 都有一个子的reducer
    // combineReducers 合并的时候传入一个对象 key是属性名 值是处理函数

    let reducers = {};
    for(let m of _app._models){
      // key 是namespace的值 value是一个reducer函数
      reducers[m.namespace] = function(state=m.state,action){
        // action => 一般都是 'count/add'等等
        // 截取action / 后面名字  查看m.namespace 当前的reducers下是否有该名字(方法)
        let actionType = action.type;
        let [namespace,type] = actionType.split('/');
        // 当action里命名空间  和 当前方法命名空间 相同 才需要处理
        if(namespace == m.namespace){
          let reducer = m.reducers[type];
          // 此时说明有该方法 就执行
          if(reducer){
            return reducer(state,action)
          }
        }
          return state;
      }
    }
    let reducer = combineReducers(reducers);
    let sagaMiddleware = createSagaMiddleware()
    function* rootSaga(){
      for(let m of _app._models){
        for(let key in (m.effects)||{}){
          //监听每个动作发生,当动作发生的时候 执行对应的generator 任务
          // takeEvery(action,fn)
          // 第三个参数 effects 传递给 *asyncAdd()的第二个参数
          yield effects.takeEvery(m.namespace+'/'+key,m.effects[key],effects)
        }
      }
    }
    let store = createStore(reducer,applyMiddleware(sagaMiddleware))
    sagaMiddleware.run(rootSaga)
    ReactDom.render(
      <Provider store={store}>
        {App}
      </Provider>
      ,document.querySelector(root))
  }

  return _app
}

## 装饰器(高阶组件)
```js
@testable
class Person{

}
function testable(target){
  target.testable = true
}

console.log(Person.testable)

dva配合ant

  • antd 按需引入
    • npm install antd babel-plugin-import --save
  • 然后在 .webpackrc 中添加如下配置:
{
  "extraBabelPlugins": [
    ["import", {
      "libraryName": "antd",
      "libraryDirectory": "es",
      "style": true
    }]
  ]
}

DOM DIFF

  • DOM Diff比较两个虚拟DOM区别 比较两个对象的区别

  • dom diff作用 根据2个虚拟对象创建出补丁,描述改变的内容,将这个补丁更新dom

  • 差异计算

  • 先序深度优先遍历

  • 三种优化策略