组件和复用技术是构建大型应用的必要利器,Vue和Reactjs等框架都很好的支持了组件化开发。本章节重点学习Reactjs的组件化,下面以搜索页面demo介绍:
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中。
新建一个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;
最终的效果如下:
这里仅仅将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)=>
(
{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;
效果如下:
2、更新state
Vue和Reactjs作为MVVM模式,数据驱动DOM的更新和渲染。在Vue中,我们知道通过监听数据的变化,VDOM的diff算法,最终映射到真实DOM的更新。在Reactjs中,其过程是类似的,我们看下如何实现。
在"大家都在搜"中,我们增加"下一批"功能,当点击时,更新hotList数组,页面DOM也及时更新。效果如下:
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;
上面讲到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方法。
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中如何将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组件中,从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;
看下最终的渲染效果:
至此,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的变相实现"插槽"功能。