React组件化01

2019-10-28  本文已影响0人  LM林慕

此文项目代码:https://github.com/bei-yang/I-want-to-be-an-architect
码字不易,辛苦点个star,感谢!

引言


此篇文章主要涉及以下内容:

  1. react组件化
  2. 容器组件 VS 展示组件
  3. 高阶组件
  4. PureComponent
  5. render props
  6. 异步渲染组件
  7. 函数化组件Hooks

学习资源


组件


React没有Vue那么多api,基本全部都是组件,react的开发模式,大体可以用一个公式表达

UI=F(state)

试用ant-design组件库


安装:npm i antd -S
试用button

import React,{Component} from 'react'
import Button from 'antd/lib/button'
import 'antd/dist/antd.css'

class App extends Component{
  render(){
    return (
      <div className='App'>
        <Button type='primary'>Button</Button>
      </div>
    )
  }
}

export default App

配置按需加载


安装react-app-rewired取代 react-scripts,可以扩展webpack的配置,类似vue.config.js

npm i react-app-rewired@2.0.2-next.0 babel-plugin-import --save
// config-overrides.js  根目录
const { injectBabelPlugin } = require("react-app-rewired");
module.exports = function override(config, env) {
  config = injectBabelPlugin(
    // 在默认配置基础上注入
    // 插件名,插件配置
    ["import", { libraryName: "antd", libraryDirectory: "es", style: "css" }],
    config
  );

  return config;
};

容器组件 VS 展示组件


基本原则:容器组件负责数据获取,展示组件负责根据props显示信息
优势:

  1. 如何工作和如何展示分离
  2. 重用性高
  3. 更高的可用性
  4. 更易于测试
import React, { Component } from "react";
// 容器组件
export default class CommentList extends Component {
 constructor(props) {
  super(props);
  this.state = {
   comments: []
 };
}
 componentDidMount() {
  setTimeout(() => {
   this.setState({
    comments: [
    { body: "react is very good", author: "facebook" },
    { body: "vue is very good", author: "youyuxi" }
   ]
  });
 }, 1000);
}
 render() {
  return (
   <div>
   {this.state.comments.map((c, i) => (
     <Comment key={i} data={c} />
   ))}
   </div>
 );
}
}
// 展示组件
function Comment({ data }) {
 return (
  <div>
   <p>{data.body}</p>
   <p> --- {data.author}</p>
  </div>
);
}

PureComponent

代码尽量写成展示组件,使用PureComponent
定制了shouldComponentUpdate后的Component(浅比较)。

class Comp extends React.PureComponent{

}

缺点是必须要用class形式,只能传值类型的数据,或者引用地址不能改变,因为内部只会做一个浅比较。

import shallowEqual from './shallowEqual'
import Component from './Component'

export default function PureComponent(props, context) {
  Component.call(this, props, context)
}

PureComponent.prototype = Object.create(Component.prototype)
PureComponent.prototype.constructor = PureComponent
PureComponent.prototype.isPureReactComponent = true
PureComponent.prototype.shouldComponentUpdate = shallowCompare

function shallowCompare(nextProps, nextState) {
  return (
    !shallowEqual(this.props, nextProps) || !shallowEqual(this.state, nextState)
  )
}
export default function shallowEqual(objA, objB) {
  if (objA === objB) {
    return true
  }
  if (
    typeof objA !== 'object' ||
    objA === null ||
    typeof objB !== 'object' ||
    objB === null
  ) {
    return false
  }
  var keysA = Object.keys(objA)
  var keyB = Object.keys(objB)

  if (keysA.length !== keysB.length) {
    return false
  }
  // test for A's keys different from b
  for (var i = 0; i < keys.length; i++) {
    if (!objB.hasOwnProperty(keysA[i]) || objA[keysA[i]] !== objB[keysA[i]]) {
      return false
    }
  }
  return true
}

React.memo


react v16.6.0之后的版本,可以使用React.memo让函数式的组件也有PureComponent的功能

// memo高阶组件
const Joke = React.memo(() => {
  <div>
    {this.props.value || 'loading...'}
  </div>
));

高阶组件


React里就有了HOC(Higher-Order Components)的概念
高阶组件也是一个组件,但是他返回另外一个组件,产生新的组件可以对属性进行包装,甚至重写部分生命周期

const withKai = (Component) => {
  const NewComponent = (props) => {
    return <Component {...props} name="这是高阶组件" />;
  };
  return NewComponent;
};

上面的withKai组件,其实就是代理了Component,只是多传递了一个name参数

高阶链式调用


高阶组件最巧妙的一点,是可以链式调用。
但是这种写法会比较累赘,推荐使用装饰器的写法。

import React, { Component } from 'react'
import { Button } from 'antd'

const withKai = Component => {
  const NewComponent = props => {
    return <Component {...props} name="高阶组价" />
  }
  return NewComponent
}

const withLog = Component => {
  class NewComponent extends React.component {
    render() {
      return <Component {...this.props} />
    }
    componentDidMount() {
      console.log('didMount', this.props)
    }
  }
  return NewComponent
}

class App extends Component {
  render() {
    return (
      <div className="App">
        <h2>hi,{this.props.name}</h2>
        <Button type="primary"></Button>
      </div>
    )
  }
}

export default withKai(withLog(App))

高阶组件装饰器写法


ES7装饰器可用于简化高阶组件写法

npm i --save-dev babel-plugin-transform-decorators-legacy

安装插件,更改配置

const { injectBabelPlugin } = require("react-app-rewired");
module.exports = function override(config, env) {
  config = injectBabelPlugin(
    // 在默认配置基础上注入
    // 插件名,插件配置
    ["import", { libraryName: "antd", libraryDirectory: "es", style: "css" }],
    config
  );

  config = injectBabelPlugin(
    ["@babel/plugin-proposal-decorators", { legacy: true }],
    config
  );

  return config;
};

使用装饰器

import React, { Component } from 'react'
import { Button } from 'antd'

const withKai = Component => {
  const NewComponent = props => {
    return <Component {...props} name="高阶组价" />
  }
  return NewComponent
}

const withLog = Component => {
  class NewComponent extends React.component {
    render() {
      return <Component {...this.props} />
    }
    componentDidMount() {
      console.log('didMount', this.props)
    }
  }
  return NewComponent
}

@withKai
@withLog
class App extends Component {
  render() {
    return (
      <div className="App">
        <h2>hi,{this.props.name}</h2>
        <Button type="primary"></Button>
      </div>
    )
  }
}

export default App

组件复合-Composition

复合组件给与你足够的敏捷去定义自定义组件的外观和行为,而且是以一种明确和安全的方式进行。如果组件间有公用的非UI逻辑,将他们抽取为JS模块导入使用而不是继承它。
等同于vue中的slot

// Dialog作为容器不关心内容和逻辑
// props.footer就相当于是具名插槽,props.children相当于匿名插槽
// children是固定的(取决于使用者传进来的是什么)
function Dialog(props){
  return <div style={{border:'4px solid blue'}}>
              {props.children}  
              <div className='footer'>
                {props.footer}
            </div>
}
// WelcomeDialog通过复合提供内容
function WelcomeDialog(props){
  return (
    <Dialog {...props}>
      <h1>你好</h1>
      <p>嘿嘿...</p>
    </Dialog>
  )
}

const Api = {
 getUser(){
    return {name:'jerry',age:20}
  } 
}

function Fetcher(props){
  const user = Api[props.name]()
  return pops.children(user)  // 这里的children是一个函数
}

// 过滤器组件
function Filter({children,type}){
  return (
    <div>
      {React.Children.map(children,child => {
        if(child.type !== type){
          return;
        }
          return child;
      })}
  )
}

// 修改children
function RadioGroup(props){
  return (
    <div>
      {React.Children.map(props.children,child=>{
        // 返回东西叫做虚拟dom,是不可更改的,若要更改的话,需克隆一个新的进行更改
        React.cloneElement(child,{name:props.name})
      })}
    </div>
  )
}
function Radio({children,...rest}){
  return (
    <label htmlFor=''>
      <input type='radio' {...props} />
      {children}
    </label>
  )
}
export default function(){
  const footer=<button onClick={()=>alert('确定')}></button>
  return (
    <div>
      <Fetcher name='getUser'>
        {({name,age})=>(
          <p>
            {name}-{age}
          </p>
        )}
      </Fetcher>
    </div>
  )
  {/* <WelcomeDialog footer={footer} />*/}
  {/* 过滤器,可以过滤出指定标签类型 */}
  <Filter type='p'>
    <h1>react</h1>
    <p>vue</p>
  </Filter>
  {/* 修改children */}
  <RadioGroup name='mvvm'>
    <Radio value='vue'>vue</Radio>
    <Radio value='vue'>react</Radio>
    <Radio value='vue'>angular</Radio>
  </RadioGroup>
}
上一篇下一篇

猜你喜欢

热点阅读