• 前端组件化思想与实践


     前端组件化思想与实践

    image.png

    组件化思想

    • 什么是组件化?
      • 简单的说组件就是:将一段UI样式和其对应的功能作为独立的整体去看待,无论这个整体放在哪里去使用,它都具有一样的功能和样式,从而实现复用,这种整体化的思想就是组件化
    • 为什么要组件化?
      • 增加复用性灵活性,提高系统设计,从而提高开发效率。

    盖房子

    要想理解这些概念是什么以及如何使用它们,我们先来理解一个小示例。就先盖个房子

    image.png

    组件化

    将 UI 分解成多个组件。例如,我们可以这样来拆分房子:

     

    image.png

    将房子拆分成多个组件,分别完成各个组件后,通过组合便成盖好了房子

    1 <div>
    2   <Roof />     // 房顶
    3   <Wall />     //4   <Window />   //5   <Door />     //6 </div>

      

    组件化实践

    理解了组件化思想,接下来我们进一步学习组件化实践

    组件分类

    React 组件有很多种分类方式,常见的分类方式有:

    • 函数组件和类组件
    • 无状态组件和有状态组件
    • 展示型组件和容器型组件
    • 受控组件和非受控组件
    • 高阶组件

    真正弄明白这几种分类方式,对于页面的组件划分、组件之间的解耦是大有裨益的。

     

    函数组件和类组件

    函数组件(Functional Component )和类组件(Class Component),划分依据是根据组件的定义方式。函数组件使用函数定义组件,类组件使用ES6 class定义组件。下面是函数组件和类组件的简单示例:

     1 // 函数组件(Functional Component )
     2 function Welcome(props) {
     3   return <h1>Hello, {props.name}</h1>;
     4 }
     5 
     6 // 类组件(Class Component)
     7 class Welcome extends React.Component {
     8   render() {
     9     return <h1>Hello, {this.props.name}</h1>;
    10   }
    11 }

    两种写法等价,主要区别是:

     

    1. 函数组件的写法要比类组件简洁,
    2. 类组件比函数组件功能更加强大。类组件可以维护自身的状态变量,即组件的state,类组件还有不同的生命周期方法,可以让开发者能够在组件的不同阶段(挂载、更新、卸载),对组件做更多的控制。

    类组件有这么多优点,是不是我们在开发中应该首选使用类组件呢?

     

    1. 其实不然。函数组件更加专注和单一,承担的职责也更加清晰,它只是一个返回React 元素的函数,只关注对应UI的展现。函数组件接收外部传入的props,返回对应UI的DOM描述,仅此而已。
    2. 当然,如上面例子所示,使用只包含一个render方法的类组件,可以实现和函数组件相同的效果。但函数组件的使用可以从思想上迫使你在设计组件时多做思考,更加关注逻辑和显示的分离,设计出更加合理的页面上组件树的结构。

    实际操作上,当一个组件不需要管理自身状态时,可以把它设计成函数组件,当你有足够的理由发现它需要“升级”为类组件时,再把它改造为类组件。因为函数组件“升级”为类组件是有一定成本的,这样就会要求你做这个改造前更认真地思考其合理性,而不是仅仅为了一时的方便就使用类组件。(画外:新特性hooks很强大可以弥补函数组件的不足)

    无状态组件和有状态组件

    无状态组件(Stateless Component )和有状态组件(Stateful Component),划分依据是根据组件内部是否维护state。无状态组件内部不使用state,只根据外部组件传入的props返回待渲染的React 元素。有状态组件内部使用state,维护自身状态的变化,有状态组件根据外部组件传入的props和自身的state,共同决定最终返回的React 元素。

    很容易知道,函数组件(不使用hooks的情况下)一定是无状态组件,类组件则既可以充当无状态组件,也可以充当有状态组件。但如上文所述,当一个组件不需要管理自身状态时,也就是无状态组件,应该优先设计为函数组件。

    展示型组件和容器型组件

    展示型组件(Presentational Component)和容器型组件(Container Component),划分依据是根据组件的职责。

    展示型组件的职责是:

    组件UI长成什么样。展示型组件不关心组件使用的数据是如何获取的,以及组件数据应该如何修改,它只需要知道有了这些数据后,组件UI是什么样子的即可。外部组件通过props传递给展示型组件所需的数据和修改这些数据的回调函数,展示型组件只是它们的使用者。展示型组件一般是无状态组件,不需要state,因为展示型组件不需要管理数据,但当展示型组件需要管理自身的UI状态时,例如控制组件内部弹框的显示与隐藏,是可以使用state的,这时的state属于UI state。既然大部分情况下展示型组件不需要state,应该优先考虑使用函数组件实现展示型组件。

    容器型组件的职责是:

    组件数据如何工作。容器型组件需要知道如何获取子组件所需数据,以及这些数据的处理逻辑,并把数据和逻辑通过props提供给子组件使用。容器型组件一般是有状态组件,因为它们需要管理页面所需数据。

    例如,下面的例子中,UserListContainer是一个容器型组件,它获取用户列表数据,然后把用户列表数据传递给展示型组件UserList,由UserList负责UI的展现。

     1 class UserListContainer extends React.Component{
     2   constructor(props){
     3     super(props);
     4     this.state = {
     5       users: []
     6     }
     7   }
     8   
     9   componentDidMount() {
    10     var that = this;
    11     fetch('/path/to/user-api').then(function(response) {
    12       response.then(function(data) {
    13         that.setState({users: data})
    14       });
    15     });
    16   }
    17 
    18   render() {
    19     return (
    20       <UserList users={this.state.users} />
    21     )
    22   }
    23 }
    24 
    25 function UserList(props) {
    26   return (
    27     <div>
    28       <ul className="user-list">
    29         {props.users.map((user) => {
    30           return (
    31             <li key={user.id}>
    32               <span>{user.name}</span>
    33             </li>
    34           );
    35         })}
    36       </ul>
    37     </div>
    38   )  
    39 }

    另外展示型组件和容器型组件是可以互相嵌套的,展示型组件的子组件既可以包含展示型组件,也可以包含容器型组件,容器型组件也是如此。例如,当一个容器型组件承担的数据管理工作过于复杂时,可以在它的子组件中定义新的容器型组件,由新组件分担数据的管理。展示型组件和容器型组件的划分完全取决于组件所做的事情。

    组件通信

    深入了解组件分类后我们开始学习组件间的通信方法

    • 父组件向子组件传值
    • 父组件调用子组件的方法
    • 子组件传值调用父组件的方法
    • 公共store调用组件内的方法

    父组件向子组件传值

    父组件传值

    class App extends Component {
    
      public render() {
        return (
          <div className="App">
            <ChildComponent
              num={1}
            />
          </div>
        );
      }
      
    }
    
    export default App;

    子组件通过props接收父组件传递的值

    interface IChildComponent {
      num:number
    }
    
    class ChildComponent extends Component<IChildComponent> {
    
      public render() {
        const {num} = this.props;
        return (
          <div className="App">
            {num}
          </div>
        );
      }
      
    }
    
    export default ChildComponent;

    父组件调用子组件的方法

    父组件通过绑定子组件 this 指向到child上,获取调用子组件方法的能力

    class App extends Component {
      
      public child: any = {};
    
      public handleChild = () => {
        this.child.handleSelect()
      }
      
      public render() {
        return (
          <div className="App">
            <ChildComponent
              onRef={e => this.child = e}
              num={1}
            />
                
            <button onClick={this.handleChild.bind(this)}>调用子组件事件</button>
          </div>
        );
      }
      
    }
    
    export default App;

    子组件挂载上this

    interface IChildComponent {
      num:number;
      onRef?:any;
    }
    
    class ChildComponent extends Component<IChildComponent> {
      
      public constructor(props) {
        super(props);
        if (this.props.hasOwnProperty('onRef')) {
          // 存在则执行
          this.props.onRef(this);
        }
      }
      
      public handleSelect = () => {
        console.log('handleSelect');
      }
      
      public render() {
        const {num} = this.props;
        return (
          <div className="App">
            {num}
          </div>
        );
      }
    
    }
    
    export default ChildComponent;

    子组件传值调用父组件的方法

    父组件

    class App extends Component {
      
      public child: any = {};
    
      public handleChild = () => {
        this.child.handleSelect()
      }
      
      public handleShow = (data) => {
        console.log(data)
      }
      
      public render() {
        return (
          <div className="App">
            <ChildComponent 
              onShow={this.handleShow.bind(this)}
              onRef={e => this.child = e}
              num={1}
            />
                
            <button onClick={this.handleChild.bind(this)}>调用子组件事件</button>
          </div>
        );
      }
      
    }
    
    export default App;

    子组件通过调用props的函数并传递参数,实现子组件传值调用父组件方法

    interface IChildComponent {
      num:number;
      onRef?:(e)=>{};
      onShow?:(data)=>{};
    }
    
    class ChildComponent extends Component<IChildComponent> {
      
      public constructor(props) {
        super(props);
        if (this.props.hasOwnProperty('onRef')) {
          // 存在则执行
          this.props.onRef(this);
        }
      }
    
      public handleSelect = (data) => {
        console.log('handleSelect');
      }
      
      public handleShow = (data) => {
        this.props.onShow(data);
      }
      
      public render() {
        const {num} = this.props;
        return (
          <div className="App">
            {num}
            <button onClick={this.handleShow.bind(this,'hello')}>调用父组件onShow事件</button> 
          </div>
        );
      }
    
    }
    
    export default ChildComponent;

    公共store调用组件内的方法

    父组件

    export const handleOnSearch = async() => {
      // @ts-ignore
      await App.handleOnSearch()
    };
    
    class App extends Component {
      
      public child: any = {};
    
      public constructor(props) {
        super(props);
        // @ts-ignore
        App.handleOnSearch = this.handleOnSearch.bind(this)
      }
    
      public handleOnSearch = async() => {
        await this.child.handleSelect();
      };  
    
      public handleChild = () => {
        this.child.handleSelect()
      }
      
      public handleShow = (data) => {
        console.log(data)
      }
      
      public render() {
        return (
          <div className="App">
            <ChildComponent 
              onShow={this.handleShow.bind(this)}
              onRef={e => this.child = e}
              num={1}
            />
                
            <button onClick={this.handleChild.bind(this)}>调用子组件事件</button>
          </div>
        );
      }
      
    }
    
    export default App;

    store调用组件内的handleOnSearch方法

    import { handleOnSearch } from '@/pages/Publish/Demo';
    import { action, observable } from 'mobx';
    
    class DemoStore {
      
      @action.bound
      public async handleDemo(){
        await handleOnSearch()
      }
      
    }
    
    export default new DemoStore();
  • 相关阅读:
    数据库里面的表空间,用户在实际开发中的使用
    并行编程的模型机制
    临时表在开发中的使用

    HIbernate编程模型
    网络数据如何可靠传输
    Spring Security编程模型
    java的缓存框架
    sort函数使用自定义数据排序使用
    FailOver的机制
  • 原文地址:https://www.cnblogs.com/piaobodewu/p/11349496.html
Copyright © 2020-2023  润新知