当前位置:   article > 正文

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

js的state图

一、前言

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

二、组件封装方法

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

1、函数模式

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

  1. function App() {
  2. return (
  3. <div className="App">
  4. <header className="App-header">
  5. ...
  6. </header>
  7. </div>
  8. );
  9. }

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

2、类模式

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

  1. class App extends React.Component {
  2. render(){
  3. return (
  4. <div className="App">
  5. <header className="App-header">
  6. ...
  7. </header>
  8. </div>
  9. );
  10. }
  11. };

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

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

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

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

  1. import React from 'react';
  2. class SearchBox extends React.Component{
  3. render(){
  4. return(
  5. <div style={{width:300}}>
  6. <input type="text"></input>&nbsp;&nbsp;<button>搜索</button>
  7. </div>
  8. )
  9. }
  10. }
  11. export default SearchBox

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

  1. import React from 'react';
  2. import './SearchList.css';
  3. class SearchList extends React.Component{
  4. render(){
  5. return(
  6. <div className="d">
  7. <div className="s">搜索结果:</div>
  8. <ul className="u">
  9. </ul>
  10. </div>
  11. )
  12. }
  13. };
  14. export default SearchList;

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

  1. import React from 'react';
  2. import './SearchHot.css';
  3. class SearchHot extends React.Component{
  4. render(){
  5. return (
  6. <div className="d">
  7. <div className="s">大家都在搜:</div>
  8. <ul className="su">
  9. </ul>
  10. </div>
  11. )
  12. }
  13. }
  14. export default SearchHot;

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

  1. import React from 'react';
  2. import logo from './logo.svg';
  3. import './App.css';
  4. //导入两个组件
  5. import SearchList from './SearchList.js';
  6. import SearchBox from './SearchBox.js';
  7. import SearchHot from './SearchHot.js'
  8. class App extends React.Component {
  9. render(){
  10. return (
  11. <div className="App">
  12. {/* 引入组件 */}
  13. <SearchBox />
  14. <SearchHot />
  15. <SearchList />
  16. </div>
  17. );
  18. }
  19. };
  20. export default App;

最终的效果如下:

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

三、state

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

1、初始化state

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

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

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

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

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

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

  1. const hotList = this.state.hotItem.map((value)=>
  2. (
  3. <li key={value.toString()}>
  4. {value}
  5. </li>
  6. )
  7. )

在JSX引入该对象表达式。

  1. return (
  2. <div className="d">
  3. <div className="s">大家都在搜:</div>
  4. <ul className="su">
  5. {/*引用变量表达式 */}
  6. {hotList}
  7. </ul>
  8. </div>
  9. )

完整的代码如下:

  1. import React from 'react';
  2. import './SearchHot.css';
  3. class SearchHot extends React.Component{
  4. constructor(props){
  5. super(props);
  6. this.state = {hotItem:['口罩','手套','酒精']}
  7. }
  8. render(){
  9. const hotList = this.state.hotItem.map((value)=>
  10. (
  11. <li key={value.toString()}>
  12. {value}
  13. </li>
  14. )
  15. )
  16. return (
  17. <div className="d">
  18. <div className="s">大家都在搜:</div>
  19. <ul className="su">
  20. {hotList}
  21. </ul>
  22. </div>
  23. )
  24. }
  25. }
  26. 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数据。

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

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

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

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

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

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

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

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

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

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

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

其结果如下:

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

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

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

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

完整的代码如下:

  1. import React from 'react';
  2. import './SearchHot.css';
  3. class SearchHot extends React.Component{
  4. constructor(props){
  5. // 1、调用父类构造方法
  6. super(props);
  7. //2、初始化state对象
  8. this.state = {hotItem:['口罩','手套','酒精',]};
  9. //绑定this
  10. this.changeHot = this.changeHot.bind(this);
  11. }
  12. changeHot(){
  13. this.setState({hotItem:['电视','手机','电脑','平板']
  14. })
  15. }
  16. //箭头函数
  17. //changeHot = () =>this.setState({hotItem:['电视','手机','电脑','平板']});
  18. render(){
  19. //1、定义变量,使用state对象数据,构造列表
  20. const hotList = this.state.hotItem.map((value)=>
  21. (<li key={value.toString()}>
  22. {value}
  23. </li>)
  24. )
  25. return (
  26. <div className="d">
  27. <div className="s">大家都在搜:<a href="#" onClick={this.changeHot} style={{marginLeft:100}}>下一批</a></div>
  28. <ul className="su">
  29. {/*引用变量表达式 */}
  30. {hotList}
  31. </ul>
  32. </div>
  33. )
  34. }
  35. }
  36. 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方式获取,后面我们会讲到)

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

(2)button增加onclick事件,

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

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

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

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

SearchBox.js的完整代码:

  1. import React from 'react';
  2. class SearchBox extends React.Component{
  3. constructor(props){
  4. super(props);
  5. this.state = {searchVal:""};
  6. this.onSearchClick = this.onSearchClick.bind(this);
  7. this.handleChange = this.handleChange.bind(this);
  8. }
  9. //1、获取input输入的值,保存到state
  10. handleChange(e){
  11. this.setState({searchVal:e.target.value});
  12. }
  13. //2、回调changeSearchVal,将searchVal传递给父组件
  14. onSearchClick(){
  15. this.props.changeSearchVal(this.state.searchVal);
  16. }
  17. render(){
  18. return(
  19. <div style={{width:300,margin:10}}>
  20. <input type="text" style={{width:150}} onChange={this.handleChange}></input>&nbsp;&nbsp;<button onClick={this.onSearchClick}>搜索</button>
  21. </div>
  22. )
  23. }
  24. }
  25. export default SearchBox
  • App组件

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

 <SearchBox changeSearchVal = {this.changeSearchVal}/>

changeSearchVal方法的实现如下;

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

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

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

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

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

App.js的完整代码如下:

  1. import React from 'react';
  2. import logo from './logo.svg';
  3. import './App.css';
  4. //导入两个组件
  5. import SearchList from './SearchList.js';
  6. import SearchBox from './SearchBox.js';
  7. import SearchHot from './SearchHot.js'
  8. class App extends React.Component {
  9. constructor(props){
  10. super(props);
  11. //初始化state
  12. this.state={searchListVal:[]};
  13. this.changeSearchVal = this.changeSearchVal.bind(this);
  14. }
  15. //1、回调方法,
  16. changeSearchVal(val){
  17. //查询搜索结果,为了简化,写死固定值,模拟查询过程
  18. console.log("输入值:"+val);
  19. let searchListVal=['牛奶','饼干']
  20. //更新state中的searchListVal
  21. this.setState({searchListVal:searchListVal});
  22. }
  23. render(){
  24. return (
  25. <div className="App">
  26. {/* 通过props传递回调方法changeSearchVal */}
  27. <SearchBox changeSearchVal = {this.changeSearchVal}/>
  28. <SearchHot />
  29. {/* 通过props传递属性值searchListVal */}
  30. <SearchList searchListVal={this.state.searchListVal}/>
  31. </div>
  32. );
  33. }
  34. };
  35. export default App;
  • SearchList组件

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

  1. import React from 'react';
  2. import './SearchList.css';
  3. class SearchList extends React.Component{
  4. constructor(props){
  5. super(props);
  6. }
  7. render(){
  8. //1、从props中获取searchListVal,封装列表
  9. const searchValList = this.props.searchListVal.map((value)=>
  10. (<li key={value.toString()}>
  11. {value}
  12. </li>)
  13. );
  14. return(
  15. <div className="d">
  16. <div className="s">搜索结果:</div>
  17. <ul className="u">
  18. {/* 引用对象表达式*/}
  19. {searchValList}
  20. </ul>
  21. </div>
  22. )
  23. }
  24. };
  25. export default SearchList;

看下最终的渲染效果:

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

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

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

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

  1. render(){
  2. //修改props的值
  3. this.props.searchListVal=['牛奶','饼干'];
  4. //1、从props中获取searchListVal,封装列表
  5. const searchValList = this.props.searchListVal.map((value)=>
  6. (<li key={value.toString()}>
  7. {value}
  8. </li>)
  9. );
  10. ...
  11. }

结果报错了

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

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

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

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

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

  1. return(
  2. <div className="d">
  3. <div className="s">搜索结果:</div>
  4. {/* 引入插槽代码 */}
  5. {this.props.children}
  6. <ul className="u">
  7. {/* 引用对象表达式*/}
  8. {searchValList}
  9. </ul>
  10. </div>
  11. )

五、总结

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

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

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

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

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

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

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/知新_RL/article/detail/63419
推荐阅读
相关标签
  

闽ICP备14008679号