赞
踩
组件和复用技术是构建大型应用的必要利器,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> <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可以看做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;
上面讲到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进行处理和转发。
(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> <button onClick={this.onSearchClick}>搜索</button>
- </div>
- )
- }
- }
- export default SearchBox
-
-
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组件中,从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的变相实现"插槽"功能。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。