React 进阶

Junfeng Liu

2018-05-11

Return type of render() method

  • null. Renders nothing.
  • Booleans. Renders nothing.
       (To support
    return test && <Child /> pattern.)
  • String and numbers. These are rendered as text nodes in the DOM.
  • Portals. Created with ReactDOM.createPortal.
  • React element. Typically created via JSX.
  • Array. Return multiple items, since React 16.0.
  • Fragment. Since React 16.2.

Fragments

render() {
  return (
    <React.Fragment>
      <ChildA />
      <ChildB />
      <ChildC />
    </React.Fragment>
  );
}


// Short syntax available in Babel v7.0.0-beta.31+
class Columns extends React.Component {
  render() {
    return (
      <>
        <td>Hello</td>
        <td>World</td>
      </>
    );
  }
}

Portals

render() {
  // React does *not* create a new div. It renders the children into `domNode`.
  // `domNode` is any valid DOM node, regardless of its location in the DOM.
  return ReactDOM.createPortal(
    this.props.children,
    domNode,
  );
}

Portals provide a first-class way to render children into a DOM node that exists outside the DOM hierarchy of the parent component.

New context API in React 16.3

const ThemeContext = React.createContext('light');

class ThemeProvider extends React.Component {
  state = {theme: 'light'};

  render() {
    return (
      <ThemeContext.Provider value={this.state.theme}>
        {this.props.children}
      </ThemeContext.Provider>
    );
  }
}

class ThemedButton extends React.Component {
  render() {
    return (
      <ThemeContext.Consumer>
        {theme => <Button theme={theme} />}
      </ThemeContext.Consumer>
    );
  }
}

New React lifecycle methods

Higher Order Components

  • Props Proxy
function ppHOC(WrappedComponent) {
  return class PP extends React.Component {
    render() {
      const newProps = {
        user: currentLoggedInUser
      }
      return <WrappedComponent {...this.props} {...newProps}/>
    }
  }
}

Higher Order Components

  • Inheritance Inversion
function iiHOC(WrappedComponent) {
  return class Enhancer extends WrappedComponent {
    render() {
      if (this.props.loggedIn) {
        return super.render()
      } else {
        return null
      }
    }
  }
}

Higher Order Components

  • HOC and parameters
function HOCFactoryFactory(...params){
  // do something with params
  return function HOCFactory(WrappedComponent) {
    return class HOC extends React.Component {
      render() {
        return <WrappedComponent {...this.props}/>
      }
    }
  }
}
HOCFactoryFactory(params)(WrappedComponent)

Function as Child Component

const Foo = ({ children }) => {
  return children('foo');
};

<Foo>
  {(name) => <div>`hello from ${name}`</div>}
</Foo>

Function as Prop Component

const Foo = ({ hello }) => {
  return hello('foo');
};

const hello = (name) => {
  return <div>`hello from ${name}`</div>;
};

<Foo hello={hello} />

Component Injection

const Foo = ({ Hello }) => {
  return <Hello name="foo" />;
};

const Hello = ({ name }) => {
  return <div>`hello from ${name}`</div>;
};

<Foo Hello={Hello} />

Functional setState

setState(stateChange[, callback])

this.setState({quantity: 2});

performs a shallow merge of stateChange into the new state

setState(updater[, callback])

this.setState((prevState, props) => {
  return {counter: prevState.counter + props.step};
});

updater is a function with the signature:
                       (prevState, props) => stateChange

Functional setState

function increase (state, props) {
  return {value : state.value + props.step};
}

function decrease (state, props) {
  return {value : state.value - props.step};
}


class Counter extends Component {
  state = {value: 0};

  handleIncrement = () => {
    this.setState(increase);
  }

  handleDecrement = () => {
    this.setState(decrease);
  }

  render() {
    return (
       <div>
          <button onClick={this.handleIncrement}>+</button>
          <h1>{this.state.value}</h1>
          <button onClick={this.handleDecrement}>-</button>
       </div>
    );
  }
}

Declare state changes separately from the component classes.

CSS in JS

写 CSS 代码时都有哪些痛点:
 

  • 全局污染 – CSS 的选择器是全局生效的,所以在 class 名称比较简单时,容易引起全局选择器冲突,导致样式互相影响。
  • 命名混乱 – 因为怕全局污染,所以日常起 class 名称时会尽量加长,这样不容易重复,但当项目由多人维护时,很容易导致命名风格不统一。
  • 样式重用困难 – 有时虽然知道项目上已有一些相似的样式,但因为怕互相影响,不敢重用。
  • 代码冗余 – 由于样式重用的困难性等问题,导致代码冗余。

CSS in JS

styled-components

import React from 'react';
import styled, { css } from 'styled-components';

// Create a <Title> react component
const Title = styled.h1`
  font-size: 1.5em;
  text-align: center;
  color: palevioletred;
`;

// Create a <Wrapper> react component that renders a <section> with
// some padding and a papayawhip background
const Wrapper = styled.section`
  padding: 4em;
  background: papayawhip;
`;

// Use them like any other React component – except they're styled!
<Wrapper>
  <Title>Hello World, this is my first styled component!</Title>
</Wrapper>


const Button = styled.button`
  border-radius: 3px;
  padding: 0.25em 1em;
  margin: 0 1em;
  background: transparent;
  color: palevioletred;
  border: 2px solid palevioletred;

  ${props => props.primary && css`
    background: palevioletred;
    color: white;
  `}
`;

const Link = ({ className, children }) => (
  <a className={className}>
    {children}
  </a>
)

const StyledLink = styled(Link)`
  color: palevioletred;
  font-weight: bold;
`;

render(
  <div>
    <Link>Unstyled, boring Link</Link>
    <br />
    <StyledLink>Styled, exciting Link</StyledLink>
  </div>
);

https://www.styled-components.com/

styled-components

Lightweight React Alternatives

名称 大小 (gzip) 特点
React 49KB
Preact 3KB 体积小,高性能,兼容 IE9+
Inferno 8KB 高性能,无状态组件支持生命期事件
react-lite 13KB 对 React 的兼容程度高,只支持 JSX
Nerv 9KB 京东开发,高性能,兼容 IE8

从 React 切换到 Nerv

{
  resolve: {
    alias: {
      'react': 'nervjs',
      'react-dom': 'nervjs'
    }
  }
}
  • Webpack
{
    "plugins": [
        ["module-resolver", {
            "root": ["."],
            "alias": {
                "react": "nervjs",
                "react-dom": "nervjs"
            }
        }]
    ]
}
  • Babel

Parcel bundler 

  • 零配置
  • 编译速度更快
  • 热更新
yarn global add parcel-bundler
parcel index.html



yarn add --dev parcel-bundler

// package.json
"scripts": {
  "start": "parcel index.html",
  "build": "parcel build index.html"
}

yarn start
<html>
<body>
  <script src="./index.js"></script>
</body>
</html>

index.html

Storybook

  • 开发过程中即时预览 React 组件
  • 可绕过 UI 跳转逻辑,伪造输入参数
  • 有很多的插件
import React from 'react';
import { storiesOf } from '@storybook/react';
import { action } from '@storybook/addon-actions';
import Button from '../components/Button';

storiesOf('Button', module)
  .add('with text', () => (
    <Button onClick={action('clicked')}>Hello Button</Button>
  ))
  .add('with some emoji', () => (
    <Button onClick={action('clicked')}>
      <span role="img" aria-label="so cool">😀 😎 👍 💯</span>
    </Button>
  )); 

前端工程

  • 打包:速度快,支持热更新
  • 配置文件:不同的配置环境导出不同的内容
  • 前端路由
  • REST API 调用
  • GraphQL 调用
  • 对话框:Alert, Toast, Modal
  • 矢量图标: 插入 SVG,并可用 CSS 修改样式
  • 移动端适配方案

参考资料:

React 进阶

By Liu Junfeng

React 进阶

  • 603