react系列

3.react-router 和react-router-dom的区别  https://blog.csdn.net/sinat_17775997/article/details/69218382

4.Route关联component有多种形式(render、component、children) children定义形式与render和component的不同在于,children的执行与match无关,即使match为null,children函数也是会执行的
(1) component 会重复加载,每次重新调用React.createElement(component, props)
(2) render 内联渲染,不会重新加载
5.插入html元素

 6.redux-thunk

redux-thunk实现了相关异步流程内聚到redux的流程中,实现middleware的功能,也便于项目的开发与维护,避免冗余代码。而实现的方式便是改写redux中的dispatch API,使其可以除PlainObject外,接受一个函数作为参数。

//action.js
const custom = (data) => {
  type: 'SET_CUSTOM_DATA',
  data: data    
}

dispatch(custom({}))

//redux-thunk

const custom = (data) => {
  return async (dispatch, getState) => {
     //异步操作
     dispatch({
        type: 'SET_CUSTOM_DATA',
        data: data 
    })
  }
    
}
dispatch(custom({}))

7. react-redux中<Provider>组件的作用,以及如何实现异步加载reducer

import { Provider } from 'react-redux'
import { createStore } from 'redux'
import todoApp from './reducers'
import App from './components/App'

let store = createStore(todoApp);

render(
  <Provider store={store}>
    <App />
  </Provider>,
  document.getElementById('root')
)
//App的所有子组件就默认都可以拿到state
//它的原理是React组件的context属性

class Provider extends Component {
  //store放在了上下文对象context上面。然后,子组件就可以从context拿到store
  getChildContext() {
    return {
      store: this.props.store
    };
  }
  render() {
    return this.props.children;
  }
}

Provider.childContextTypes = {
  store: React.PropTypes.object
}        


//子组件调用
class VisibleTodoList extends Component {
  componentDidMount() {
    const { store } = this.context;
    this.unsubscribe = store.subscribe(() =>
      this.forceUpdate()
    );
  }

  render() {
    const props = this.props;
    const { store } = this.context;
    const state = store.getState();
    // ...
  }
}

VisibleTodoList.contextTypes = {
  store: React.PropTypes.object
}

//react组件的context属性应用,将store嵌入,通过this.context.store获取
//injectReducer在项目中异步加载reducers,具体的reducer可以在生命周期函数中加载进去

 8.  react-redux

export default connect(function (state, props){
  /*return {
    ...state,
    name: [state.name, props.name]
  };*/
  // state, props如果同名,可以通过这个函数决定是获取哪个
  return state.user;
}, {
  setName(name){
    return {
      type: SET_NAME,
      name
    };
  },
  addAge(n){
    return {
      type: ADD_AGE,
      n
    }
  }
})(App);

 9. 高阶组件

//withHoc.js
import React, { Component } from 'react';

export default (params) => (WrappedComponent) => {
    return class From extends Component {
        //方便使用react-devtool调试时显示不同组件名
        static displayName = `From(${WrappedComponent.name || WrappedComponent.displayName})`
        render() {
          return (
            <div className="withHoc">
              <div>{params}</div>
              <WrappedComponent {...this.props} {...this.state}></WrappedComponent>
            </div>
          );
        }
      }
}

//a.js
import React, { Component } from 'react';
import withHoc from './withHoc.js';

class A extends Component {
  render() {
    return (
      <div className="A">
        <div>A component</div>
      </div>
    );
  }
}

export default withHoc('a')(A);

 10. withRouter

目的就是让被修饰的组件可以从属性中获取history,location,match,
路由组件可以直接获取这些属性,而非路由组件就必须通过withRouter修饰后才能获取这些属性了,
比如 <Route path='/' component={App}/>

App组件就可以直接获取路由中这些属性了,但是,如果App组件中如果有一个子组件Foo,那么Foo就不能直接获取路由中的属性了,必须通过withRouter修饰后才能获取到。

//用于js实现路由跳转
this.props.history.push('/chat)

 11. Redux DevTools 扩展的使用说明

if (process.env.NODE_ENV === 'development') {
  const devToolsExtension = window.__REDUX_DEVTOOLS_EXTENSION__

  if (typeof devToolsExtension === 'function') {
    enhancers.push(devToolsExtension())
  }
}

const composedEnhancers = compose(
  applyMiddleware(...middlewares),
  ...enhancers
)

//或者
const enhancers = []
let composeEnhancers = compose

// 在development模式,使用redux-devtools-extension
if (process.env.NODE_ENV === 'development') {
  if (typeof window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ === 'function') {
    composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__
  }
}
const store = createReduxStore(
  makeRootReducer(),
  initialState,
  composeEnhancers(
    applyMiddleware(...middleware),
    ...enhancers
  )
)

12. 常用npm包

connected-react-router
core-js 
reselect // computed
redux-undo // 撤销,重做
react-loadable //懒加载

13. px2rem

// config-overrides.js
const px2rem=require('postcss-px2rem-exclude')

module.exports = {
  webpack: override(

    // 用js的方式导入antd及其样式:style为true表示导入antd.less; 为false表示不使用js的方式导入antd.less或antd.css;为'css'表示使用antd.css;
    fixBabelImports("import", {
      libraryName: "antd-mobile", libraryDirectory: "es", style: true  // 为false或css会导致addLessLoader失效
    }),
    addLessLoader({
      javascriptEnabled: true,
      // modifyVars: { "@primary-color": "#D24545" } // 深红色
    }),
    addPostcssPlugins([
      px2rem({
        remUnit: 75,

        // 仅排除对antd-mobile的px2rem转化
        exclude: /node_modules\/antd-mobile/i
      })
    ]),
    disableEsLint() // 取消eslint检查,加快yarn start速度
  ),
  devServer: overrideDevServer(
    // dev server plugin
    watchAll()
  )
}
// 某一项不想转为rem
border: 1px solid #ccc; /*no*/

vue-cli3用的是postcss-plugin-px2rem; 实现原理一样。重要!! 如果个别地方不想转化px。可以简单的使用大写的 PX 或 Px

14. withRouter导致组件重复渲染

React Router 4 把Route当作普通的React组件,可以在任意组件内使用Route;

withRouter 为非路由组件提供了location,history,match三个参数;但是有时会发现有些接口会重复调用,这个是由于组件重新渲染的原因

经过Redux connect后的Home组件,在更新阶段,会使用浅比较,但是由于Route组件导致这个失效

componentWillReceiveProps(nextProps, nextContext) {
    warning(
      !(nextProps.location && !this.props.location),
      '<Route> elements should not change from uncontrolled to controlled (or vice versa). You initially used no "location" prop and then provided one on a subsequent render.'
    )

    warning(
      !(!nextProps.location && this.props.location),
      '<Route> elements should not change from controlled to uncontrolled (or vice versa). You provided a "location" prop initially but omitted it on a subsequent render.'
    )

    // 注意这里,computeMatch每次返回的都是一个新对象,如此一来,每次Route更新,setState都会重新设置一个新的match对象
    this.setState({
      match: this.computeMatch(nextProps, nextContext.router)
    })
  }

  render() {
    const { match } = this.state
    const { children, component, render } = this.props
    const { history, route, staticContext } = this.context.router
    const location = this.props.location || route.location
    // 注意这里,这是传递给Route中的组件的属性
    const props = { match, location, history, staticContext }

    if (component)
      return match ? React.createElement(component, props) : null

    if (render)
      return match ? render(props) : null

    if (typeof children === 'function')
      return children(props)

    if (children && !isEmptyChildren(children))
      return React.Children.only(children)

    return null
  }

这样,每次Route更新(componentWillReceiveProps被调用),都将创建一个新的match;

导致Redux的浅比较失败,进而触发组件的重新渲染

解决方法:1⃣️. connected-react-router 2⃣️. 直接引用history.js文件

15.input 在IE11,不触发onchange

<input
  type='text'
  value={this.state.value}
  onCompositionStart={this.handleComposition}
  onCompositionUpdate={this.handleComposition}
  onCompositionEnd={this.handleComposition}
  onChange={this.handleChange}
/>

//前2个事件都在onChange之前触发,onCompositionEnd是在onChange之后触发。
//如果直接输入完成是不会触发这三个事件的,只有onChange事件。比如直接输入英文

// ie11下中文输入法会不触发onChange,所以也需要setState,否则此时会发现中文输入进去后输入框没有变换

/**
   * 中文输入法,选词
   */
  handleComposition = (e) => {
    this.isOnComposition = e.type !== 'compositionend'
    if (!this.isOnComposition) {
      //ie11不触发onchange导致中文不展示
      this.setState({
        value: e.target.value
      })
      this.handleInputAndSearch(e.target.value)
    }

 16. 如果map的组件为受控组件,则使用索引并不会产生问题,但是如果为非受控组件,例如input等,则会由于复用标签元素导致value并未更改

17. setState机制https://github.com/sisterAn/blog/issues/26

批处理的原因,举例来说,如果我们在浏览器中click处理,都ChildParent调用setState,我们不想重新渲染Child两次

补充,这里输出 0,0,3,4

componentDidMount() {
      this.setState((prevState, props) => ({
        val: prevState.val + 1
      }))
      console.log(this.state.val)
      this.setState((prevState, props) => ({
        val: prevState.val + 1
      }))
      console.log(this.state.val)
  
      setTimeout(() => {
        this.setState({val: this.state.val + 1});
        console.log(this.state.val);  // 第 3 次 log
  
        this.setState({val: this.state.val + 1});
        console.log(this.state.val);  // 第 4 次 log
      }, 0);

    }
// 0,1 "logbefore", 2 "log",3,4
componentDidMount() {
      this.setState((prevState, props) => ({
        val: prevState.val + 1
      }))
      console.log(this.state.val)
      Promise.resolve().then(() => {
        console.log(this.state.val, 'logbefore');
        this.setState({val: this.state.val + 1});
        console.log(this.state.val, 'log');
      })
  
      setTimeout(() => {
        this.setState({val: this.state.val + 1});
        console.log(this.state.val);  // 第 3 次 log
  
        this.setState({val: this.state.val + 1});
        console.log(this.state.val);  // 第 4 次 log
      }, 0);

    }

(图片链接来源:https://stackoverflow.com/questions/48563650/does-react-keep-the-order-for-state-updates/48610973#48610973

 

 源码路径(v16.9.0)

/react/packages/react-test-renderer/src/ReactShallowRenderer.js

class Updater {
  constructor(renderer) {
    this._renderer = renderer;
    this._callbacks = [];
  }
  //...
  enqueueSetState(publicInstance, partialState, callback, callerName) {
    this._enqueueCallback(callback, publicInstance);
    const currentState = this._renderer._newState || publicInstance.state;

    if (typeof partialState === 'function') {
      partialState = partialState.call(
        publicInstance,
        currentState, //这里确保每次的state都是当时最新的
        publicInstance.props,
      );
    }

    // Null and undefined are treated as no-ops.
    if (partialState === null || partialState === undefined) {
      return;
    }

    this._renderer._newState = {
      ...currentState,
      ...partialState,
    };

    this._renderer.render(this._renderer._element, this._renderer._context);
  }
}

 /react/src/renderers/shared/stack/reconciler/ReactUpdates.js

function enqueueUpdate(component) {
  ensureInjected();

  // Various parts of our code (such as ReactCompositeComponent's
  // _renderValidatedComponent) assume that calls to render aren't nested;
  // verify that that's the case. (This is called by each top-level update
  // function, like setState, forceUpdate, etc.; creation and
  // destruction of top-level components is guarded in ReactMount.)

  if (!batchingStrategy.isBatchingUpdates) {// 还是根据isBatchingUpdates
    batchingStrategy.batchedUpdates(enqueueUpdate, component);
    return;
  }

  dirtyComponents.push(component);
  if (component._updateBatchNumber == null) {
    component._updateBatchNumber = updateBatchNumber + 1;
  }
}

https://juejin.im/post/5c5c33aef265da2d8d69b639#heading-3

posted @ 2019-01-02 23:52  潮哥  阅读(247)  评论(0编辑  收藏  举报