react 总结和技巧

目录

  • 1 netlify
  • 2 Github Pages
  • 3 无状态组件(函数式组件)
  • 4 优化 PureComponent
  • 5 Fragment
  • 6 context
  • 7 高阶组价
    7.1 使用高阶组件重构
    7.2 实际项目使用高阶优化1
    7.3 参考高阶代码
  • 8 react-powerplug
  • 9 高阶组件,处理多个平级组件
  • 10 propTypes 、defaultProps
  • 11 Render Props
  • 12 Error Boundary
  • 13 bind this
  • 14 16.3 Context
  • 15 使用16.3 Context 改写 redux
  • 16 可变数据
  • 17 this.props.children
  • 18 随记

1 netlify

netlify 是一个提供托管静态网站的服务。

官网:https://app.netlify.com/

部署
更改名称

2 Github Pages

部署到 Github Pages(Github Pages是面向用户、组织和项目开放的公共静态页面搭建托管服务)

步骤:

npm install gh-pages --save-dev 

  • 在 package.json 增加 homepage
"homepage": "https://hongzelin.github.io/GitHubPageTest",

说明:https://用户名.github.io/仓库名


  • 在 package.json 在 scripts 增加 deploy
"scripts": {
 "predeploy": "yarn run build",  //  这个是在 执行 deploy命令之前执行,打出静态包,放在 gh-pages 新分支上 
  "deploy": "gh-pages -d build"  // 参考 gh-pages 官网
}

  • 运行
yarn run deploy

说明:这时候在GitHub项目仓库会打出来新的分支 gh-pages,
接下来,直接访问 homepage 配置的网址即可。

3 无状态组件(函数式组件)

参考:https://www.jianshu.com/p/63569386befc

理解基础概念:

import React from 'react';

const Header = (props) => {
  return (
    <div className="container">
      <div className="row">
        <div className="col-xs-1 col-xs-offset-11">
          <h1>Header</h1>
        </div>
      </div>
    </div>
  );
};

export default Header;


说明:无状态组件,也叫函数式组件;定义一个箭头函数,传入 props 参数。
只有 return,没有 render 方法;

注意:如果需要操作状态,这时候需要配合高阶组价一起使用。
参考:https://zhuanlan.zhihu.com/p/24776678?group_id=802649040843051008


4 优化 PureComponent

组件继承 PureComponent ,避免组件重复渲染

import React, { Component, PureComponent } from 'react';
import logo from './logo.svg';
import './App.css';

// 无状态组件一般写法,在 props 没有改变的时候,也会重复渲染
// const Temp = (props) => {
//   console.log('render Temp');
//   return (
//     <div>{ props.val }</div>
//   );
// }

// 使用 es6 的写法,继承 PureComponent ,解决在 props 没有改变的时候,重复渲染问题
class Temp extends PureComponent {
  render() {
    console.log('render Temp');
    return (
      <div>{ this.props.val }</div>
    );
  }
}

class App extends Component {
  state = {
    val: 1
  }

  componentDidMount() {
    setInterval(() => { // 演示,值没有改变的时候,无状态组件是否还是会重新渲染。
      this.setState({
        val: 1
      })
    }, 2000)
  }

  // shouldComponentUpdate(nextProps, nextState) {
  //   console.log('nextState', nextState);
  //   console.log('current state', this.state);
  //   return (
  //     this.state.val === nextState.val ? false : true
  //   )
  // }

  render() {
    console.log('render App');
    return (
      <div className="App">
        <header className="App-header">
          <img src={logo} className="App-logo" alt="logo" />
          <h1 className="App-title">Welcome to React</h1>
        </header>
        <p className="App-intro">
          To get started, edit <code>src/App.js</code> and save to reload.
        </p>
        <Temp val={ this.state.val } />
      </div>
    );
  }
}

export default App;

5 Fragment

Fragment 是为了解决需要根节点报错问题;

使用 Fragment,在 DOM 生成并不会引入多余的标签;

另一个方法也可以使用:<> ... </> ,包裹解决;

import React, { Component, Fragment } from 'react';
import logo from './logo.svg';
import './App.css';

const Temp = (props) => {
  return (
    <Fragment>
      <li>list 1</li>
      <li>list 1</li>
    </Fragment>
  )
}

class App extends Component {
  render() {
    return (
      {/* ul的子节点一定是li */}
      <ul className="App">
        <Temp />
      </ul>
    );
  }
}

export default App;

6 context

参考:https://segmentfault.com/a/1190000002878442

通过 context 传递属性的方式可以大量减少,显式通过 props 逐层传递属性的方式。这样可以减少组件之间的直接依赖关系。

import React, { Component } from 'react';
import './App.css';
import PropTypes from 'prop-types';

const Topic = (props) => {
  return (
    <div>
      <Comment />
    </div>
  )
}

const Comment = (props, context) => {
  return (
    <div>{ context.color }</div>
  )
}

Comment.contextTypes = { // 子组件需要指定 contextTypes 
  color: PropTypes.string
}

class App extends Component {
  getChildContext() {
    return { color: "red" };
  }
  render() {
    return (
      <div className="App">
        <Topic />
      </div>
    );
  }
}

App.childContextTypes = { // 父组件需要制定childContextTypes 
  color: PropTypes.string
}

export default App;

7 高阶组价

参考:
https://www.jianshu.com/p/0aae7d4d9bc1
https://zhuanlan.zhihu.com/p/24776678?group_id=802649040843051008

简单理解,高阶组价就是传入一个组件参数,返回一个组件。

import React, { Component } from 'react';
import './App.css';

const PropsLogger = (WrapperComponent) => {
  return class extends Component {
    render() {
      return <WrapperComponent { ...this.props } />
    }
  }
}

// PropsLogger 高阶组件传入一个组件函数,作为参数。
const Hello = PropsLogger((props) => {
  return (
    <p>Hello { props.name }</p>
  )
})

class App extends Component {
  render() {
    return (
      <div className="App">
        <Hello name="rails365" /> // 这里的参数,是传递高阶组价,Hello组件是等于高阶组件。
      </div>
    );
  }
}

export default App;


说明:简单的理解就是,高阶组价就是传入一个组件参数,返回一个组件。

7.1 使用高阶组件重构

重构前

  • src/components/User.js
import React, { Component } from 'react';

class User extends Component {
  constructor() {
    super();
    this.state = {
      loading: true,
      user: null
    };
  }

  componentDidMount() {
    fetch('https://randomuser.me/api/')
      .then(res => res.json())
      .then(user => {
        this.setState({
          loading: false,
          user: user
        });
      })
  }

  render() {
    if(this.state.loading) {
      return (
        <div>loading</div>
      )
    } else {
      return (
        <h1>{this.state.user.results[0].email}</h1>
      )
    }
  }
}

export default User;

  • src/components/Joke.js
import React, { Component } from 'react';

class Joke extends Component {
  constructor() {
    super();
    this.state = {
      loading: true,
      jokes: null
    };
  }

  componentDidMount() {
    fetch('http://api.icndb.com/jokes/random/3')
      .then(res => res.json())
      .then(jokes => {
        this.setState({
          loading: false,
          jokes: jokes
        });
      })
  }

  render() {
    if(this.state.loading) {
      return (
        <div>loading</div>
      )
    } else {
      return (
        <div>
          {
            this.state.jokes.value.map(joke => (
              <p key={ joke.id }>{ joke.joke }</p>
            ))
          }
        </div>
      )
    }
  }
}

export default Joke;


重构后:使用高阶组件

  • src/hoc/withFetch.js
import React, { Component } from 'react';

const withFetch = (url) => (View) => {
  return class extends Component {
    constructor() {
      super();
      this.state = {
        loading: true,
        data: null
      };
    }

    componentDidMount() {
      fetch(url)
        .then(res => res.json())
        .then(data => {
          this.setState({
            loading: false,
            data: data
          });
        })
    }

    render() {
      if(this.state.loading) {
        return (
          <div>loading</div>
        )
      } else {
        return <View data={ this.state.data } />
      }
    }
  }
}

export default withFetch;

  • src/components/Joke.js(调用高阶组件)
import React from 'react';
import withFetch from '../hoc/withFetch';

const Joke = withFetch('http://api.icndb.com/jokes/random/3')(props => {
  return (
    <div>
      {
        props.data.value.map(joke => (
          <p key={ joke.id }>{ joke.joke }</p>
        ))
      }
    </div>
  )
})

export default Joke;


  • src/components/User.js
import React from 'react';
import withFetch from '../hoc/withFetch';

const User = withFetch('https://randomuser.me/api/')(props => {
  return (
    <h1>{props.data.results[0].email}</h1>
  )
})

export default User;


7.2 实际项目使用高阶优化1

页面引用不同的组件,关键点,传入给各个组件 props 属性!!!

import React from 'react'
import OperatorUI from './OperatorUI'
import StartUI from './StartUI'
import EndUI from './EndUI'
import StrategyUI from './StrategyUI'


  getRenderSortFlow(info, styleObj) {
    let { type } = info;
    type = type.indexOf('strategy') > -1 ? 'strategy' : type;

    switch (type) {
      case 'start': // 开始节点
        return (
          <StartUI
            info={info}
            styleObj={styleObj}
            eventObj={this.eventObj}
            key={info.uuid}
            workFlowName={'start'}
          />
        );
      case 'end': // 结束节点
        return (
          <EndUI
            info={info}
            styleObj={styleObj}
            eventObj={this.eventObj}
            key={info.uuid}
            workFlowName={'end'}
          />
        );
      case 'algorithm': // 算子
        return (
          <OperatorUI
            info={info}
            styleObj={styleObj}
            eventObj={this.eventObj}
            key={info.uuid}
            workFlowName={'algorithm'}
          />
        );
      case 'strategy': // 策略
        return (
          <StrategyUI
            info={info}
            styleObj={styleObj}
            eventObj={this.eventObj}
            key={info.uuid}
            workFlowName={'strategy'}
          />
        );
      default:
        return (
          <div
            key={info.uuid}
            id={info.id}
            style={styleObj}
            data-type={info.type}
          >
            <span className="viso-name" key={info.name}>{info.name}</span>
          </div>
        );
    }
  }


StartUI 组件:

import React from 'react'
import HocNodeUI from '../HocUI/HocNodeUI'

const StartUI = HocNodeUI(({ info }) => (
  <div className="wrap-content" key={info.uuid}>
    <span className="icon-wrap icon-wrap__bg">
      <i className="iconfont icon-begin" />
    </span>
    <span className="content-name">
      {info.name}
    </span>
  </div>
))

export default StartUI;


EndUI 组件:

import React from 'react'
import HocNodeUI from '../HocUI/HocNodeUI'

const EndUI = HocNodeUI(({ info }) => (
  <div className="wrap-content" key={info.uuid}>
    <span className="icon-wrap icon-wrap__bg">
      <i className="iconfont icon-end" />
    </span>
    <span className="content-name">
      {info.name}
    </span>
  </div>
))

export default EndUI;

OperatorUI 组件:

import React from 'react'
import { Icon } from '@ali/wind'
import HocNodeUI from '../HocUI/HocNodeUI'

const del = (e, id, eventObj) => {
  e.stopPropagation();
  if (id) {
    eventObj.onRemove(id);
  }
}

const OperatorUI = HocNodeUI(({ info, eventObj }) => (
  <div className="wrap-content" key={info.uuid}>
    <span className="icon-wrap">
      <Icon type="data" className="icon-item" size={'xs'} />
    </span>
    <span className="content-name">
      {info.name}
    </span>
    <Icon
      type="close"
      className="icon-close"
      size={'xs'}
      onClick={e => del(e, info.uuid, eventObj)}
    />
  </div>
))

export default OperatorUI;


StrategyUI 组件:

import React from 'react'
import { Icon } from '@ali/wind'
import HocNodeUI from '../HocUI/HocNodeUI'
import './StrategyUI.less'

const del = (e, id, eventObj) => {
  e.stopPropagation();
  if (id) {
    eventObj.onRemove(id);
  }
}

const StrategyUI = HocNodeUI(({ info, eventObj }) => (
  <div className="wrap-content">
    <span className="strategy-logo">
      &#60;&#8260;&#62;
    </span>
    <span className="content-name">
      {info.name}
    </span>
    <Icon
      type="close"
      className="icon-close"
      size={'xs'}
      onClick={e => del(e, info.uuid, eventObj)}
    />
  </div>
))

export default StrategyUI;


/*
 * @Author: lin.zehong
 * @Date: 2019-06-13 10:22:11
 * @Last Modified by: lin.zehong
 * @Last Modified time: 2019-06-13 13:58:50
 * @Desc: 高阶:开始、结束、算子、策略等UI高阶
 */

import React, { Component } from 'react'
import { connect } from 'dva'
import PropTypes from 'prop-types'
import { selectors } from '../../../models/tasksFlow'
import './HocNodeUI.less'

const HocNodeUI = (WrappedComponent) => {
  const mapStateToProps = state => ({
    isActiveAlgo: selectors.getIsActiveAlgo(state),
  })

  @connect(mapStateToProps, null)
  class HocUI extends Component {
    static propTypes = {
      eventObj: PropTypes.objectOf(PropTypes.any),
      info: PropTypes.objectOf(PropTypes.any),
      styleObj: PropTypes.objectOf(PropTypes.any),
      isActiveAlgo: PropTypes.string,
    }

    render() {
      const { info, styleObj, eventObj, isActiveAlgo } = this.props;
      return (
        <div
          key={info.uuid}
          id={info.uuid}
          className={`l-entity-wrap l-o-wrap ${isActiveAlgo === info.uuid ? 'isActive' : ''}`}
          style={styleObj}
          onClick={(e) => {
            eventObj.onClick(e, info)
          }}
          onMouseUp={(e) => {
            eventObj.onMouseUp(e, info)
          }}
        >
          <WrappedComponent {...this.props} />
        </div>
      )
    }
  }
  return HocUI;
}

export default HocNodeUI;


思路流程图

注意:
1:除了使用高阶包裹的方式,组件使用高阶也可以使用注解的方式:

import React from 'react'
import PropTypes from 'prop-types'
import HocNodeUI from '../HocUI/HocNodeUI'

@HocNodeUI
class EndUI extends React.Component {
  render() {
    const { info } = this.props;
    return (
      <div className="wrap-content" key={info.uuid}>
        <span className="icon-wrap icon-wrap__bg">
          <i className="iconfont icon-end" />
        </span>
        <span className="content-name">
          {info.name}
        </span>
      </div>
    )
  }
}

EndUI.propTypes = {
  info: PropTypes.objectOf(PropTypes.any),
}

// const EndUI = HocNodeUI(({ info }) => (
//   <div className="wrap-content" key={info.uuid}>
//     <span className="icon-wrap icon-wrap__bg">
//       <i className="iconfont icon-end" />
//     </span>
//     <span className="content-name">
//       {info.name}
//     </span>
//   </div>
// ))

export default EndUI;


2:传递参数给高阶组件

/*
 * @Author: lin.zehong
 * @Date: 2019-06-13 10:22:11
 * @Last Modified by: lin.zehong
 * @Last Modified time: 2019-06-13 15:15:09
 * @Desc: 高阶:开始、结束、算子、策略等UI高阶
 */

import React, { Component } from 'react'
import { connect } from 'dva'
import PropTypes from 'prop-types'
import { selectors } from '../../../models/tasksFlow'
import './HocNodeUI.less'

const HocNodeUI = (class_name) => (WrappedComponent) => {
  const mapStateToProps = state => ({
    isActiveAlgo: selectors.getIsActiveAlgo(state),
  })

  @connect(mapStateToProps, null)
  class HocUI extends Component {
    static propTypes = {
      eventObj: PropTypes.objectOf(PropTypes.any),
      info: PropTypes.objectOf(PropTypes.any),
      styleObj: PropTypes.objectOf(PropTypes.any),
      isActiveAlgo: PropTypes.string,
    }

    render() {
      const { info, styleObj, eventObj, isActiveAlgo } = this.props;
      return (
        <div
          key={info.uuid}
          id={info.uuid}
          className={`l-entity-wrap ${class_name} ${isActiveAlgo === info.uuid ? 'isActive' : ''}`}
          style={styleObj}
          onClick={(e) => {
            eventObj.onClick(e, info)
          }}
          onMouseUp={(e) => {
            eventObj.onMouseUp(e, info)
          }}
        >
          <WrappedComponent {...this.props} />
        </div>
      )
    }
  }
  return HocUI;
}

export default HocNodeUI;

方式一:使用注释,也就是高阶的参数:

import React from 'react'
import PropTypes from 'prop-types'
import HocNodeUI from '../HocUI/HocNodeUI'

@HocNodeUI('l-o-wrap')
class EndUI extends React.Component {
  render() {
    const { info } = this.props;
    return (
      <div className="wrap-content" key={info.uuid}>
        <span className="icon-wrap icon-wrap__bg">
          <i className="iconfont icon-end" />
        </span>
        <span className="content-name">
          {info.name}
        </span>
      </div>
    )
  }
}

EndUI.propTypes = {
  info: PropTypes.objectOf(PropTypes.any),
}

··········································································

方式二:使用高阶包裹方式:

import React from 'react'
import { Icon } from '@ali/wind'
import HocNodeUI from '../HocUI/HocNodeUI'

const del = (e, id, eventObj) => {
  e.stopPropagation();
  if (id) {
    eventObj.onRemove(id);
  }
}

const OperatorUI = HocNodeUI('l-o-wrap')(({ info, eventObj }) => (
  <div className="wrap-content" key={info.uuid}>
    <span className="icon-wrap">
      <Icon type="data" className="icon-item" size={'xs'} />
    </span>
    <span className="content-name">
      {info.name}
    </span>
    <Icon
      type="close"
      className="icon-close"
      size={'xs'}
      onClick={e => del(e, info.uuid, eventObj)}
    />
  </div>
))

export default OperatorUI;

··········································································

方式三:如果不需要传递参数,也是需要括号调用:

import React from 'react'
import { Icon } from '@ali/wind'
import HocNodeUI from '../HocUI/HocNodeUI'
import './StrategyUI.less'

const del = (e, id, eventObj) => {
  e.stopPropagation();
  if (id) {
    eventObj.onRemove(id);
  }
}

const StrategyUI = HocNodeUI()(({ info, eventObj }) => (
  <div className="wrap-content">
    <span className="strategy-logo">
      &#60;&#8260;&#62;
    </span>
    <span className="content-name">
      {info.name}
    </span>
    <Icon
      type="close"
      className="icon-close"
      size={'xs'}
      onClick={e => del(e, info.uuid, eventObj)}
    />
  </div>
))

export default StrategyUI;


7.3 参考高阶代码

import React, { Component } from 'react'
import PropTypes from 'prop-types'
import './HocUI.less'

/**
 * 用于包装在绘图区域拖拽生成的节点,方便后期扩展功能或者UI
 * 页面上面渲染的每个节点都需要从此高阶组件过,
 * 那么就在这里对正、反渲染的逻辑进行判断,给节点做拦截,
 * 新增一些方法、属性、样式上去
 *
 * @param WrappedComponent
 * @returns {{new(): {render(): *}}}
 * @constructor
 */
function HocNodeFlowMenu(className) {
  return function (WrappedComponent) {
    return class HocNodeFlowMenuUI extends Component {
      static propTypes = {
        info: PropTypes.objectOf(PropTypes.any),
        styleObj: PropTypes.objectOf(PropTypes.any),
        eventObj: PropTypes.objectOf(PropTypes.any),
      }

      constructor(props) {
        super(props);
        this.state = {
          visiable: false,
        }
        this.handleMouseMove = this.handleMouseMove.bind(this);
        this.handleMouseLeave = this.handleMouseLeave.bind(this);
      }

      handleMouseMove() {
        this.setState(() => ({
          visiable: true,
        }))
      }

      handleMouseLeave() {
        this.setState(() => ({
          visiable: false,
        }))
      }

      render() {
        const { info, styleObj, eventObj } = this.props;
        return (
          <div
            key={info.uuid}
            id={info.uuid}
            className={`${className} node-flow-item`}
            style={styleObj}
            onClick={() => {
              eventObj.onClick(info)
            }}
            onMouseUp={(e) => {
              eventObj.onMouseUp(e, info)
            }}
            onContextMenu={(e) => {
              eventObj.onContextmenu(e)
            }}
          >
            <WrappedComponent {...this.props} />
          </div>
        )
      }
    }
  }
}

export default HocNodeFlowMenu;


8 react-powerplug

react-powerplug 插件

  • 重构前
import React, { Component } from 'react';
import './App.css';

class Counter extends Component {
  state = {
    counter: 0
  }

  increment = () => {
    this.setState(prevState => ({ counter: prevState.counter + 1 }));
  }

  decrement = () => {
    this.setState(prevState => ({ counter: prevState.counter - 1 }));
  }

  render() {
    return (
      <div className="App">
        <div>Counter: { this.state.counter }</div>
        <button onClick={ this.increment }>Increment</button>
        <button onClick={ this.decrement }>Decrement</button>
      </div>
    );
  }
}

export default Counter;

  • 重构后
import React, { Component } from 'react';
import './App.css';
import { State } from 'react-powerplug'

class Counter extends Component {
  render() {
    return (
      <State initial={{ counter: 0 }}>
        {({ state, setState }) => (
          <div className="App">
            <div>Counter: { state.counter }</div>
            <button onClick={ () => setState({ counter: state.counter + 1 }) }>Increment</button>
            <button onClick={ () => setState({ counter: state.counter - 1 }) }>Decrement</button>
          </div>
        )}
      </State>
    );
  }
}

export default Counter;

9 高阶组件,处理多个平级组件

import React, { Component } from 'react';
import './App.css';

const Wrapper = ({ children }) => children;

const Hello = ({ name }) => {
  return (
    <Wrapper>
      <h1>React 16 rocks</h1>
      <p>Hello, { name }!</p>
    </Wrapper>
  )
}

class App extends Component {
  render() {
    return (
      <div className="App">
        <Hello name="Hackages" />
      </div>
    );
  }
}

export default App;


说明:
const Wrapper = ({ children }) => children; 这个相关于 const Wrapper = ( props ) => props.children;,这样更容易理解些吧?


10 propTypes 、defaultProps

import React, { Component } from 'react';
import './App.css';
import PropTypes from 'prop-types';

class Hello extends Component {
  // static defaultProps = {
  //   name: "rails365"
  // }

  render() {
    return (
      <div>
        <h1>Hello, { this.props.money }, { this.props.name }</h1>
        <ul>
          {
            this.props.movies.map(movie => <li key={ movie.id }>{ movie.title }</li>)
          }
        </ul>
      </div>
    )
  }
}

Hello.propTypes = {
  money: PropTypes.number,
  onChange: PropTypes.func.isRequired,
  name: PropTypes.oneOfType([
    PropTypes.string,
    PropTypes.number
  ]),

// movies是个数组,PropTypes.shape表示数组里面的每个元素是个对象,再对里面的对象进行细致限制。
  movies: PropTypes.arrayOf(PropTypes.shape({ 
    id: PropTypes.number,
    title: PropTypes.string,
    visit_count: PropTypes.number
  }))
}

// Hello.defaultProps = {
//   name: "rails365"
// }

class App extends Component {
  onChange() {
  }

  state = {
    movies: [
      { id: 1, title: 'title 1', visit_count: 1 },
      { id: 2, title: 'title 2', visit_count: 2 }
    ]
  }

  render() {
    return (
      <div className="App">
        <Hello money={ 99 } movies={ this.state.movies } name={ "rails365" } onChange={ this.onChange } />
      </div>
    );
  }
}

export default App;


11 Render Props

参考:
https://zhuanlan.zhihu.com/p/31267131
https://reactjs.org/docs/render-props.html

  • 重写前
import React from 'react';

const withMouse = (Component) => {
  return class extends React.Component {
    state = { x: 0, y: 0 }

    handleMouseMove = (event) => {
      this.setState({
        x: event.clientX,
        y: event.clientY
      })
    }

    render() {
      return (
        <div style={{ height: '100%' }} onMouseMove={ this.handleMouseMove }>
          <Component { ...this.props } mouse={ this.state }/>
        </div>
      )
    }
  }
}

const App = (props) => {
  const { x, y } = props.mouse
  return (
    <div style={{ height: '100%' }}>
      <h1>The mouse position is ({ x }, { y })</h1>
    </div>
  )
}

const AppWithMouse = withMouse(App)


export default AppWithMouse;


  • 重写后
import React from 'react';
import PropTypes from 'prop-types';

class Mouse extends React.Component {
  static propTypes = {
    render: PropTypes.func.isRequired
  }

  state = { x: 0, y: 0 }

  handleMouseMove = (event) => {
    this.setState({
      x: event.clientX,
      y: event.clientY
    })
  }

  render() {
    return (
      <div style={{ height: '100%' }} onMouseMove={ this.handleMouseMove }>
        { this.props.render(this.state) }
      </div>
    )
  }
}

const Position = ({ x, y }) => {
  return (
    <h1>The mouse position is ({ x }, { y })</h1>
  )
}

const Position1 = ({ x, y }) => {
  return (
    <p>The mouse position is ({ x }, { y })</p>
  )
}

const App = () => {
  return (
    <div style={{ height: '100%' }}>
      <Mouse render={(props) => <Position { ...props } />} />
      <Mouse render={(props) => <Position1 { ...props } />} />
      <Mouse render={({x, y}) => (
          <p>The mouse position is ({ x }, { y })</p>
      )} />
    </div>
  )
}

export default App;


12 Error Boundary

针对的是其子节点,在生产环境,如果该子节点发生错误,则只有

  • src/App.js
import React, { Component } from 'react';
import './App.css';
import Broken from './Broken';
import ErrorBoundary from './ErrorBoundary';

class App extends Component {
  state = {
    counter: 0
  }

  increment = () => {
    this.setState(prevState => ({ counter: prevState.counter + 1 }));
  }

  decrement = () => {
    this.setState(prevState => ({ counter: prevState.counter - 1 }));
  }

  render() {
    return (
      <div className="App">
        <h1>Hello rails365</h1>
        <ErrorBoundary render={ (error, errorInfo) => <p>Error: { error.toString() }</p> }>
          <Broken />
        </ErrorBoundary>

        <div>Counter: { this.state.counter }</div>
        <button onClick={ this.increment }>Increment</button>
        <button onClick={ this.decrement }>Decrement</button>
      </div>
    );
  }
}

export default App;

  • src/ErrorBoundary.js
import React, { Component } from 'react';
import PropTypes from 'prop-types';

class ErrorBoundary extends Component {
  static propTypes = {
    children: PropTypes.oneOfType([
      PropTypes.node,
      PropTypes.arrayOf(PropTypes.node)
    ]).isRequired
  }

  state = {
    hasError: false,
    error: null,
    errorInfo: null
  }

  componentDidCatch(error, errorInfo) {
    this.setState({
      hasError: true,
      error: error,
      errorInfo: errorInfo
    })
  }

  render() {
    if (this.state.hasError) {
      return <div>{ this.props.render(this.state.error, this.state.errorInfo) }</div>
    }
    return this.props.children;
  }
}

export default ErrorBoundary;

13 bind this

参考:https://www.jianshu.com/p/018665bc4ce2

基本有4/5种写法

  • 最好的写法是使用es6,箭头函数的写法
import React, { Component } from 'react';
import logo from './logo.svg';
import './App.css';

class App extends Component {
  constructor(props) {
    super(props)
    this.state = {
      name: ''
    }
  }

  handleChange = e => {
    this.setState({
      name: e.target.value
    })
  }

  render() {
    return (
      <div className="App">
        <header className="App-header">
          <img src={logo} className="App-logo" alt="logo" />
          <h1 className="App-title">Welcome to React</h1>
        </header>
        <p>{ this.state.name }</p>
        <input type="text" onChange={ this.handleChange } />
      </div>
    );
  }
}

export default App;

14 16.3 Context

import React, { Component } from 'react';
import './App.css';

// 第一步,创建 context
const myContext = React.createContext()

// 第二步,创建 Provider Component

class MyProvider extends Component {
  state = {
    name: "rails365",
    age: 27
  }

  render() {
    return (
      <myContext.Provider value={{ state: this.state }}>
        { this.props.children }
      </myContext.Provider>
    )
  }
}

const Family = (props) => {
  return (
    <div>
      <h1>Family</h1>
      <Person />
    </div>
  )
}


class Person extends Component {
  render() {
    return (
      <div>
        <h1>Person</h1>
        <myContext.Consumer>
          { ({ state }) => <p>My age is { state.age }</p> }
        </myContext.Consumer>
      </div>
    );
  }
}

class App extends Component {
  render() {
    return (
      <div className="App">
        <p>Hello App</p>
        <MyProvider>
          <Family />
        </MyProvider>
      </div>
    );
  }
}

export default App;


16.3 Context 进一步理解

  • src/App.js
import React, { Component } from 'react';
import './App.css';
import { CDNFlagIcon } from "react-flag-kit";

const ThemeContext = React.createContext();

const localeMap = {
  "en-US": { locale: "en-US", flag: "US", content: "Hello, World!" },
  "fr-FR": { locale: "fr-FR", flag: "FR", content: "Bonjour le monde!" },
  "es-ES": { locale: "es-ES", flag: "ES", content: "¡Hola Mundo!" }
};

class LocaleSwitcher extends Component {
  state = localeMap["en-US"]
  render() {
    return (
      <ThemeContext.Provider
        value={{
          state: this.state,
          updateLocale: e => this.setState(localeMap[e.target.value])
        }}
      >
        { this.props.children }
      </ThemeContext.Provider>
    );
  }
}

const LocaleSelect = () => {
  return (
    <ThemeContext.Consumer>
      {context => (
        <select value={ context.state.locale } onChange={ context.updateLocale }>
          <option value="en-US">English</option>
          <option value="fr-FR">French</option>
          <option value="es-ES">Spanish</option>
        </select>
      )}
    </ThemeContext.Consumer>
  )
}

const LocaleFlag = (props) => {
  return (
    <ThemeContext.Consumer>
      { context => <CDNFlagIcon code={ context.state.flag } size={ 256 } /> }
    </ThemeContext.Consumer>
  )
}

const LocaleContent = (props) => {
  return (
    <ThemeContext.Consumer>
      { context => <h1>{ context.state.content }</h1> }
    </ThemeContext.Consumer>
  )
}

class App extends Component {
  render() {
    return (
      <LocaleSwitcher>
        <LocaleSelect />
        <LocaleFlag />
        <LocaleContent />
      </LocaleSwitcher>
    );
  }
}

export default App;

15 使用16.3 Context 改写 redux

主要思想,哪里提供数据,哪里消费数据

  • src/contexts/ReminderContext.js
import React, { Component } from 'react';
import { bake_cookie, read_cookie } from 'sfcookies';

export const ReminderContext = React.createContext();

export class ReminderProvider extends Component {
  state = {
    reminders: read_cookie("reminders") || []
  }

  addReminder = (text, dueDate) => {
    let reminders = [];
    reminders = [
      ...this.state.reminders,
      { id: Math.random(), text, dueDate }
    ];
    this.setState({
      reminders: reminders
    });
    bake_cookie("reminders", reminders);
  }

  deleteReminder = (id) => {
    let reminders = [];
    reminders = this.state.reminders.filter(reminder => reminder.id !== id);
    this.setState({
      reminders: reminders
    });
    bake_cookie("reminders", reminders);
  }

  clearReminders = () => {
    this.setState({
      reminders: []
    });
    bake_cookie("reminders", []);
  }

  render() {
    return (
      <ReminderContext.Provider
        value={{
          reminders: this.state.reminders,
          addReminder: this.addReminder,
          deleteReminder: this.deleteReminder,
          clearReminders: this.clearReminders
        }}
      >
        { this.props.children }
      </ReminderContext.Provider>
    );
  }
}

  • src/index.js
import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './components/App';
import registerServiceWorker from './registerServiceWorker';

import { ReminderContext, ReminderProvider } from './contexts/ReminderContext';

ReactDOM.render(
  <ReminderProvider>
    <ReminderContext.Consumer>
      { ({ reminders, clearReminders, addReminder, deleteReminder }) =>
        (<App clearReminders={ clearReminders } deleteReminder={ deleteReminder } addReminder={ addReminder } reminders={ reminders } />)
      }
    </ReminderContext.Consumer>
  </ReminderProvider>,
  document.getElementById('root')
);
registerServiceWorker();


  • src/components/App.js
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import moment from 'moment';

class App extends Component {
  constructor(props) {
    super(props)
    this.state = {
      text: '',
      dueDate: ''
    };
  }

  addReminder() {
    this.props.addReminder(this.state.text, this.state.dueDate);
  }

  deleteReminder(id) {
    this.props.deleteReminder(id);
  }

  clearReminders() {
    this.props.clearReminders();
  }

  renderReminders() {
    const { reminders } = this.props;
    return (
      <ul className="list-group col-sm-8 mt-2">
        {
          reminders.map(reminder => {
            return (
              <li key={ reminder.id } className="list-group-item">
                <div className="list-item">
                  <div>{ reminder.text }</div>
                  <div><em>{ moment(new Date(reminder.dueDate)).fromNow() }</em></div>
                </div>
                <div
                  className="list-item delete-button"
                  onClick={ () => this.deleteReminder(reminder.id) }
                >
                  &#x2715;
                </div>
              </li>
            );
          })
        }
      </ul>
    );
  }

  render() {
    return (
      <div className="App">
        <div className="title">Reminder Pro</div>

        <div className="form-inline">
          <div className="form-group mr-2">
            <input
              type="text"
              className="form-control mr-2"
              placeholder="I have to..."
              onChange={ (event) => this.setState({text: event.target.value}) }
            />
            <input
              type="datetime-local"
              className="form-control"
              onChange={ (event) => this.setState({dueDate: event.target.value}) }
            />
          </div>
          <button
            type="button"
            className="btn btn-success"
            onClick={ () => this.addReminder() }
          >
            Add Reminder
          </button>
        </div>
        { this.renderReminders() }
        <div 
          className="btn btn-danger mt-3"
          onClick={ () => this.clearReminders() }
        >
          Clear Reminders
        </div>
      </div>
    );
  }
}

App.propTypes = {
  reminders: PropTypes.array.isRequired,
  addReminder: PropTypes.func.isRequired,
  deleteReminder: PropTypes.func.isRequired,
  clearReminders: PropTypes.func.isRequired
}

export default App;

16 可变数据

  • 学习资源

https://medium.com/@fknussel/arrays-objects-and-mutations-6b23348b54aa

https://github.com/hfpp2012/redux-reminder-pro/blob/master/src/reducers/index.js

https://redux.js.org/recipes/structuring-reducers/immutable-update-patterns

https://lodash.com/docs/

17 this.props.children

  • 学习资源

https://reactjs.org/docs/composition-vs-inheritance.html

https://mxstbr.blog/2017/02/react-children-deepdive/#enforcing-a-single-child

https://reactjs.org/docs/jsx-in-depth.html

https://reactjs.org/docs/react-api.html#createelement

https://stackoverflow.com/questions/29464577/why-doesnt-this-props-children-map-work

https://stackoverflow.com/questions/35616029/react-createelement-vs-cloneelement

https://segmentfault.com/a/1190000008587988

https://learn.co/lessons/react-this-props-children

18 随记

AuthorizedRoute:
在应用程序中限制未登录的用户访问某些路由是非常常见的,还有对于授权和未授权的用户 UI 也可能大不一样,为了解决这样的需求,我们可以考虑为应用程序设置一个主入口
https://www.ctolib.com/topics-122237.html

PureComponent :
解决无状态组件重复渲染问题。
你可以实现shouldComponentUpdate函数来指明在什么样的确切条件下,你希望这个组件得到重绘。如果你编写的是纯粹的组件(界面完全由 props 和 state 所决定),你可以利用PureComponent来为你做这个工作。

扩展:Medium 网站访问不到

打开 hosts 文件(C:\Windows\System32\drivers\etc\hosts),向其中添加:

# Medium Start
104.16.120.127  medium.com
104.16.120.145  api.medium.com
104.16.120.145  cdn-static-1.medium.com
104.16.120.145  cdn-images-1.medium.com
104.16.120.145  cdn-images-2.medium.com
# Medium End 

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 214,588评论 6 496
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,456评论 3 389
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 160,146评论 0 350
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,387评论 1 288
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,481评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,510评论 1 293
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,522评论 3 414
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,296评论 0 270
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,745评论 1 307
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,039评论 2 330
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,202评论 1 343
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,901评论 5 338
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,538评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,165评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,415评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,081评论 2 365
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,085评论 2 352

推荐阅读更多精彩内容