玩转Reactjs第三篇-组件(模式&state&props)

一、前言

    组件和复用技术是构建大型应用的必要利器,Vue和Reactjs等框架都很好的支持了组件化开发。本章节重点学习Reactjs的组件化,下面以搜索页面demo介绍:

玩转Reactjs第三篇-组件(模式&state&props)_第1张图片

二、组件封装方法

Vue中组件是通过Vue.component()方法来创建全局组件,对于Reactjs,有如下两种方式:

1、函数模式

这种模式是通过js函数定义一个组件,也是最简单的模式。比如默认demo的App.js中定义的App方法。

function App() {
  return (
    
...
); }

该方法返回React元素(一段JSX代码)。

2、类模式

该模式使用ES6的class定义组件,我们将上面的例子改写:

class  App extends React.Component {
  render(){
    return (
    
...
); } };

与函数组件比较,首先是一个继承React.Component的父类,定义了render()方法,return代码段包裹在render()方法中。为了区别W3C默认的组件,Reactjs自定义的组件首字母需要大写。

两者组件模式在React是等效的,我们这里推荐类模式创建,其创建的组件是有的实例,后面会讲到类模式具有一些额外的特性,比如state,生命周期等等。

下面我用就利用类模式创建demo组件。从页面组成看,分三个子组件,搜索栏组件SearchBox,热搜栏组件SearchHot,以及搜索结果组件SearchList,此三个子组件都包含在父组件App中。

玩转Reactjs第三篇-组件(模式&state&props)_第2张图片

新建一个SearchBox.js,创建SearchBox组件并导出。

import React from 'react';

class SearchBox extends React.Component{
    render(){
        return(
            
  
) } } export default SearchBox

新建一个SearchList.js,创建SearchList组件。

import React from 'react';
import   './SearchList.css';

class SearchList extends React.Component{
    render(){
      return(
        
搜索结果:
) } }; export default SearchList;

新建一个SearchHot.js,创建SearcHot组件。

import React from 'react';
import   './SearchHot.css';

class SearchHot extends React.Component{
    render(){
        return (
        
大家都在搜:
) } } export default SearchHot;

App.js代码如下,首先导入SearchList,SearchBox,SearchHot组件,然后类似原生的标签进行调用和渲染。

import React from 'react';
import logo from './logo.svg';
import './App.css';
//导入两个组件
import SearchList from './SearchList.js';
import SearchBox from './SearchBox.js';
import SearchHot from './SearchHot.js'

class  App extends React.Component {
  render(){
    return (
    
{/* 引入组件 */}
); } }; export default App;

最终的效果如下:

玩转Reactjs第三篇-组件(模式&state&props)_第3张图片

这里仅仅将demo的框架轮廓画了出来,下面我们将添加相关的数据以及响应。

三、state

state可以看做Reactjs的状态机,保存该组件的相关数据和变量,state的作用域仅在组件内部,可以类比Vue的data。我们在"大家都在搜"模块,利用state构造一组热搜数据,并渲染出来。

1、初始化state

需要注意,只有类模式的组件才支持state,我们首先在SearchHot组件增加构造器函数,并初始化state对象。

constructor(props){
   // 1、调用父类构造方法
   super(props);
   //2、初始化state对象
   this.state = {hotItem:['口罩','手套','酒精']}
}

该构造方法的入参是props,可以利用该参数进行父子组件的数据传递,下一节我们再详细说明。

1、调用super方法,实现父类构造方法(React.Component)的调用。

2、初始化state对象,为简化流程,我们暂时将写死数据。

下面我们将state的数据渲染到页面区域,定义hostList对象,获取state数据,并拼装li列表(后面章节我们再仔细描述列表,暂时忽略)。

const hotList = this.state.hotItem.map((value)=>
  (
  
  • {value}
  • ) )

    在JSX引入该对象表达式。

    return (
            
    大家都在搜:
      {/*引用变量表达式 */} {hotList}
    )

    完整的代码如下:

    import React from 'react';
    import   './SearchHot.css';
    
    class SearchHot extends React.Component{
        constructor(props){
            super(props);
            this.state = {hotItem:['口罩','手套','酒精']}
        }
        render(){
            const hotList = this.state.hotItem.map((value)=>
                (
                
  • {value}
  • ) ) return (
    大家都在搜:
      {hotList}
    ) } } export default SearchHot;

    效果如下:

    玩转Reactjs第三篇-组件(模式&state&props)_第4张图片

    2、更新state

        Vue和Reactjs作为MVVM模式,数据驱动DOM的更新和渲染。在Vue中,我们知道通过监听数据的变化,VDOM的diff算法,最终映射到真实DOM的更新。在Reactjs中,其过程是类似的,我们看下如何实现。

      在"大家都在搜"中,我们增加"下一批"功能,当点击时,更新hotList数组,页面DOM也及时更新。效果如下:

    玩转Reactjs第三篇-组件(模式&state&props)_第5张图片

    JSX中添加一个a标签按钮,点击时,调用changeHot方法(关注reactjs的事件,后面章节会介绍)。

    大家都在搜:下一批

    在changeHot方法中更新hotList数据。

    changeHot(){
       this.setState({hotItem:['电视','手机','电脑','平板']})
    }

    这里要注意,需要调用setState方法更新,不能给state直接赋值,this.state={hotItem:['电视','手机','电脑','平板']},不会生效。这一点和vue不同,vue可以直接对属性进行设置。

    此时,大家点击"下一批"按钮,发现报错了。

    setState没有定义,这个是什么鬼,因为没有将回调方法绑定this对象,此时changeHot中的'this'是undefined的。有两种方式可以解决。

    第一种方法,是在构造器中绑定this

    constructor(props){
            // 1、调用父类构造方法
            super(props);
            //2、初始化state对象
            this.state = {hotItem:['口罩','手套','酒精',]};
            //绑定this
            this.changeHot = this.changeHot.bind(this);
        }

    第二种方法,将changeHot改造成箭头函数,我们知道箭头函数的this指向函数对象。

    changeHot = () =>this.setState({hotItem:['电视','手机','电脑','平板']});

    此时,点击"下一批",可以看到页面发生更新。

    我们再来做个试验,调用setSate设置hotItem后,立即打印下该值。

    changeHot(){
            this.setState({hotItem:['电视','手机','电脑','平板'] 
            })
            console.log(this.state.hotItem) 
        }

    其结果如下:

        并没有更新,why?这是因为setState是个异步操作(这点与vue是不同的,vue对于model的更新是同步的,对于view的更新是异步的,有兴趣的可以了解下vue的nexttick机制),大家可以思考下这样做的目的,如果是同步的,那么每次state更新会立即引起DOM的更新,效率将会非常低;如果是异步的,可以对设置的state进行批量操作,还可以对一些操作进行合并,大大提升效率。事实上vue的view异步更新也是如此,只不过两者实现的机制以及阶段不同而已。

    如何要获取到更新后的值呢,setState提供了设置callback方法。

        changeHot(){
            this.setState({hotItem:['电视','手机','电脑','平板'] 
            },()=>{
                console.log(this.state.hotItem)
            })
        }

    这样就能正确的获取到更新后的值了。

    完整的代码如下:

    import React from 'react';
    import   './SearchHot.css';
    
    class SearchHot extends React.Component{
        constructor(props){
            // 1、调用父类构造方法
            super(props);
            //2、初始化state对象
            this.state = {hotItem:['口罩','手套','酒精',]};
            //绑定this
            this.changeHot = this.changeHot.bind(this);
        }
        changeHot(){
            this.setState({hotItem:['电视','手机','电脑','平板'] 
            })
        }
        //箭头函数
        //changeHot = () =>this.setState({hotItem:['电视','手机','电脑','平板']});
        render(){
            //1、定义变量,使用state对象数据,构造列表
            const hotList = this.state.hotItem.map((value)=>
                (
  • {value}
  • ) ) return (
    大家都在搜:下一批
      {/*引用变量表达式 */} {hotList}
    ) } } export default SearchHot;

    四、props

         上面讲到state是组件内部的状态机,其作用域也对该组件内部。对于组件来说,父子组件的交互,数据传递是基本功能,显然state不具备实现该功能。

          我们回顾下Vue的组件间交互流程,父组件通过v-bind绑定待传的属性标签,子组件通过props属性进行接受数据;子组件通过emit方法回调父组件函数,实现子组件向父组件的数据传输。Reactjs的过程也是类似的,我们前一小节在组件构造器中提到了props入参,父组件通过props将数据传递给子组件,子组件回调父组件的方法,实现数据的上传。

    下面我们利用搜素结果模块,介绍父子组件数据流的传递,流程的示意图如下:

    玩转Reactjs第三篇-组件(模式&state&props)_第6张图片

    1、子组件SearchBox负责搜索关键字输入,保存到state中,通过回调父组件App的changeSearchVal方法,将输入值searchVal传递到父组件App。

    2、父组件App接受到searchVal后,查询搜索结果,将结果保存到state的searchListVal。

    3、父组件App将该值通过props传递到子组件SearchList,子组件SearchList接受到传递的searchListVal后,并渲染出来。

    两个子组件SearchBox与SearchList不直接交互,而是通过他们共同的父组件App进行处理和转发。

    • SearchBox组件

    (1)input的onChange监听输入值,绑定handleChange方法。

    handleChange将输入值更新到state中的searchVal(也可以用非受控组件的ref方式获取,后面我们会讲到)

    //获取input输入的值,保存到state
        handleChange(e){
           this.setState({searchVal:e.target.value});
        }

    (2)button增加onclick事件,

    获取state中的searchVal值,并通过回调父组件的changeSearchVal传递给父组件。

     //回调changeSearchVal,将searchVal传递给父组件
        onSearchClick(){
            this.props.changeSearchVal(this.state.searchVal);
        }

    我们看到使用了this.props.changeSearchVal,该方法在父组件App中定义,并通过props传递给子组件SearchBox(props不但传递属性值,也可以传递函数)。

    SearchBox.js的完整代码:

    ​
    import React from 'react';
    class SearchBox extends React.Component{
        constructor(props){
           super(props);
           this.state = {searchVal:""};
           this.onSearchClick = this.onSearchClick.bind(this);
           this.handleChange = this.handleChange.bind(this); 
        }
        //1、获取input输入的值,保存到state
        handleChange(e){
           this.setState({searchVal:e.target.value});
        }
        //2、回调changeSearchVal,将searchVal传递给父组件
        onSearchClick(){
            this.props.changeSearchVal(this.state.searchVal);
        }
        render(){
            return(
                
      
    ) } } export default SearchBox ​
    • App组件

    App中如何将changeSearchVal方法传递给子组件SearchaBox的呢?App.js中引用SeachBox的时候,增加了changeSearchVal属性(子组件通过this.props.changeSearchVal获取),其值就是该方法的引用。

     

    changeSearchVal方法的实现如下;

     changeSearchVal(val){
        //查询搜索结果,为了简化,写死固定值,模拟查询过程
        console.log("输入值:"+val);
        let searchListVal=['牛奶','饼干']
        //更新state中的searchListVal
       this.setState({searchListVal:searchListVal});
      }

    在该方法中,入参为输入框的值,为了简单起见,直接写死搜索结果,更新并保存到state的searhListVal中。

    下面将搜索结果searchListVal通过props传递给子组件searchList。

     

    引用SearchList组件,定义SearchListVal属性,并将state的searchListVal赋值给该属性。

    App.js的完整代码如下:

    import React from 'react';
    import logo from './logo.svg';
    import './App.css';
    //导入两个组件
    import SearchList from './SearchList.js';
    import SearchBox from './SearchBox.js';
    import SearchHot from './SearchHot.js'
    
    class  App extends React.Component {
      constructor(props){
        super(props);
        //初始化state
        this.state={searchListVal:[]};
        this.changeSearchVal = this.changeSearchVal.bind(this);
      }
      //1、回调方法,
      changeSearchVal(val){
        //查询搜索结果,为了简化,写死固定值,模拟查询过程
        console.log("输入值:"+val);
        let searchListVal=['牛奶','饼干']
        //更新state中的searchListVal
       this.setState({searchListVal:searchListVal});
      }
    
      render(){
        return (
        
    {/* 通过props传递回调方法changeSearchVal */} {/* 通过props传递属性值searchListVal */}
    ); } }; export default App;
    • SearchList组件

    在SearchList组件中,从props中获取searchListVal值,并封装成列表。SearchList.js代码如下:

    import React from 'react';
    import   './SearchList.css';
    
    class SearchList extends React.Component{
      constructor(props){
        super(props);
      }
        render(){
          //1、从props中获取searchListVal,封装列表
          const searchValList = this.props.searchListVal.map((value)=>
            (
  • {value}
  • ) ); return(
    搜索结果:
      {/* 引用对象表达式*/} {searchValList}
    ) } }; export default SearchList;

    看下最终的渲染效果:

    玩转Reactjs第三篇-组件(模式&state&props)_第7张图片

    至此,props的基本用法讲完了,但是对比Vue,我们发现有两个问题没有找到答案:

    1、父子组件的数据传递是否是单向的,子组件能否更改props的值呢?

    2、vue的插槽(slot)在Reactjs中如何实现?

    先来看第一个问题,我们在SearchList.js中加入这段代码,改变子组件中的props值。

    render(){
         //修改props的值
          this.props.searchListVal=['牛奶','饼干'];
          //1、从props中获取searchListVal,封装列表
          const searchValList = this.props.searchListVal.map((value)=>
            (
  • {value}
  • ) ); ... }

    结果报错了

    Props是只读的,没法修改的,这点与Vue是一致的,父子组件数据传递是单向的,且向下的。

    继续看第二个问题,实际上在Reactjs中是没有"插槽",是通过this.props.children的变相实现"插槽"功能

    在App.js中增加了一段JSX代码,

    
            
    为您搜索到2条记录

    将这段代码插入到子组件SearchList中显示。

          return(
            
    搜索结果:
    {/* 引入插槽代码 */} {this.props.children}
      {/* 引用对象表达式*/} {searchValList}
    )

    五、总结

    本章节介绍了Reactjs组件的基本知识。

    1、创建组件有两种模式,函数模式和类模式,推荐使用类模式。

    2、组件内部的状态机state,其作用域仅在其组件内部。

    (1)仅在constructor方法中可以使用this.state进行赋值,其他地方统一使用setState方法进行更新。

    (2)setState是个异步操作,如需要使用更新后的值,在其callback方法中调用。

    3、通过props实现父子组件间的数据传递,其数据流向是单向的,向下的;this.props.children的变相实现"插槽"功能。

    你可能感兴趣的:(reactjs,前端技术)