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

一、前言

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

二、组件封装方法

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

1、函数模式

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

function App() {
  return (
    <div className="App">
      <header className="App-header">
        ...
      </header>
    </div>
  );
}

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

2、类模式

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

class  App extends React.Component {
  render(){
    return (
    <div className="App">
      <header className="App-header">
        ...
      </header>
    </div>
    );
  }
};

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

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

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

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

import React from 'react';

class SearchBox extends React.Component{
    render(){
        return(
            <div style={{width:300}}>
                <input type="text"></input>&nbsp;&nbsp;<button>搜索</button>
            </div>
        )
    }
}
export default SearchBox

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

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

class SearchList extends React.Component{
    render(){
      return(
        <div className="d">
            <div className="s">搜索结果:</div>
            <ul className="u">
             </ul>
        </div>       
      )
    }
  };

export default SearchList;

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

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

class SearchHot extends React.Component{
    render(){
        return (
        <div className="d">
            <div className="s">大家都在搜:</div>
            <ul className="su">
            </ul>

        </div> 
        )
    }
}
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 (
    <div className="App">
      {/* 引入组件 */}
      <SearchBox />
      <SearchHot />
      <SearchList />
    </div>
    );
  }
};

export default App;

最终的效果如下:

这里仅仅将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)=>
  (
  <li key={value.toString()}>
         {value}
  </li>
  )
)

在JSX引入该对象表达式。

return (
        <div className="d">
            <div className="s">大家都在搜:</div>
            <ul className="su">
                {/*引用变量表达式  */}
                 {hotList}
            </ul>

        </div> 
)

完整的代码如下:

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)=>
            (
            <li key={value.toString()}>
             {value}
            </li>
            )
        )
        return (
        <div className="d">
            <div className="s">大家都在搜:</div>
            <ul className="su">
                 {hotList}
            </ul>
        </div> 
        )
    }
}
export default SearchHot;

效果如下:

2、更新state

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

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

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

<div className="s">大家都在搜:<a href="#" onClick={this.changeHot} style={{marginLeft:100}}>下一批</a></div>

在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)=>
            (<li key={value.toString()}>
             {value}
            </li>)
        )
        return (
        <div className="d"> 
            <div className="s">大家都在搜:<a href="#" onClick={this.changeHot} style={{marginLeft:100}}>下一批</a></div>
            <ul className="su">
                {/*引用变量表达式  */}
                 {hotList}
            </ul>

        </div> 
        )
    }
}
export default SearchHot;

四、props

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

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

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

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方法。

<input type="text" style={{width:150}} onChange={this.handleChange}></input>

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

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

(2)button增加onclick事件,

<button onClick={this.onSearchClick}>搜索</button>

获取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(
            <div style={{width:300,margin:10}}>
                <input type="text" style={{width:150}} onChange={this.handleChange}></input>&nbsp;&nbsp;<button onClick={this.onSearchClick}>搜索</button>
            </div>
        )
    }
}
export default SearchBox

​
  • App组件

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

 <SearchBox changeSearchVal = {this.changeSearchVal}/>

changeSearchVal方法的实现如下;

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

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

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

 <SearchList searchListVal={this.state.searchListVal}/>

引用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 (
    <div className="App">
      {/* 通过props传递回调方法changeSearchVal */}
      <SearchBox changeSearchVal = {this.changeSearchVal}/>
      <SearchHot />
      {/* 通过props传递属性值searchListVal */}
      <SearchList searchListVal={this.state.searchListVal}/>
    </div>
    );
  }
};

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)=>
        (<li key={value.toString()}>
          {value}
        </li>)
      );
      return(
        <div className="d">
            <div className="s">搜索结果:</div>
            <ul className="u">
              {/* 引用对象表达式*/}
               {searchValList}
            </ul>
        </div>       
      )
    }
  };

export default SearchList;

看下最终的渲染效果:

至此,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)=>
        (<li key={value.toString()}>
          {value}
        </li>)
      );
...
}

结果报错了

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

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

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

<SearchList searchListVal={this.state.searchListVal}>
        <div>为您搜索到2条记录</div>
</SearchList>

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

      return(
        <div className="d">
            <div className="s">搜索结果:</div>
            {/* 引入插槽代码 */}
            {this.props.children}
            <ul className="u">
              {/* 引用对象表达式*/}
               {searchValList}
            </ul>
        </div>       
      )

五、总结

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

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

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

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

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

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

发布了33 篇原创文章 · 获赞 95 · 访问量 3万+

猜你喜欢

转载自blog.csdn.net/tcy83/article/details/104269261