注:本文来自胡子大哈的文章阅读笔记
React几乎不能单独使用,不是将JQuery下载下来塞到标签中就可以使用,它在开发阶段生产阶段都需要一堆工具和库辅助,编译阶段需要借助Babel,需要Redux等第三方的状态管理工具来组织代码,如果只是要写单页面应用那也需要React-router,这就是所谓的“React.js全家桶”。当然官网也推荐了
create-react-app
工具来一键生成所需的工程目录,帮我们做好各种配置和依赖,可以达到直接使用的目的,安装好之后可以直接使用create-react-app hello-react
来创建名为hello-react的React前端工程,这个工具会直接在后台帮我们构建好最基本的配置,推荐使用淘宝定制的cnpm
命令来代替npm
搞,毕竟国内,构建流程如下(前提是已安装node.js环境,安装后默认是安装了npm
,即node包管理工具):
1.使用cnpm
(gzip压缩支持) 命令行工具代替默认的npm
# 安装cnpm命令
npm install -g cnpm --registry=https://registry.npm.taobao.org
# 设置npm源
npm config set registry https://registry.npm.taobao.org
2.构建react环境
# 安装create-react-app工具
cnpm install -g create-react-app
# 创建my-app工程
create-react-app my-app
# 进入工程目录
cd my-app
# 启动工程
npm start
默认端口为3000,可以通过访问localhost:3000
来查看刚刚运行react项目,如果要修改这个端口,可以打开package.json
文件将scripts
中的start
键值对"start": "roadhog server"
修改为"start": "set PORT=xxx&&roadhog server"
(将xxx
改成你想要的端口号即可)。React前端工程项目目录结构如下:
其中my-app\public\manifest.json
文件指定了开始页面,它是执行的源头,可以修改my-app\src\App.js
文件来更改首页内容,页面会自动刷新。
【注】
react项目拉下来后通常是没有依赖包的(即node_modules
),在往git上推的时候一般是忽略掉这个文件夹的,所以第一步应该是先拉依赖包npm install
,然后再运行。
可以肤浅的将React视作视图渲染工具,准确点说React是一个UI库,它是一个js库,不是什么前端框架,React将许多独立的块组装成一个页面(即前端组件化),虽然React在很多人的观念里优于VUE,毕竟生态圈摆在那里,但它也不是万能的,实际开发中往往需要结合其他库(如Redux、React-router等)来使用。之前学习的前端jsp,一个按钮往往会和某一大段的js代码绑定以执行相应的交互行为,然后想要复用这个按钮功能时,不但需要将按钮的内容和样式复制过来还要将对应的js代码复制过来,很麻烦,对于结构复杂的功能的交互很难复用,所以出现了组件化的概念。将复用率较高的组件抽离出来作为一个Component
,那以后所有组件都可以继承这个组件来复用该组件中的一些内容,每个组件都有自己的显示形态(即显示的内容和样式)行为,组件的显示形态和行为可以由数据状态(state)和配置参数(props)共同决定,数据状态和配置参数的改变都会影响到这个组件的显示形态。
JSX是React的核心组成,通俗点讲,JSX是使用XML标记的方式直接声明页面(有点像XML的js语法的扩展),使得组件之间可以相互嵌套,React建议使用JSX替换常规的js,有如下优点:
正常的jsp或html都是使用DOM来表示内部的元素,一个DOM的组成往往如下:
<div class='' id=''>
<div class=''>testdiv>
div>
包含了标签名、属性、子元素,其实js也可以表示这种结构,只有有些复杂而已,所以不为人知,上述的结构使用js可以表达为:
{
tag: 'div',
attrs: {className: '', id: ''},
children: [
{
tag: 'div',
attrs: {className: ''},
childern: ['test']
}
]
}
上述的方式表述不如html来的简洁明了,React改良了一下,扩展js语法(所以名字叫jsx嘛,和iPhone类似的还有一个iPhone X…),使得js可以支持直接在js中使用类似于html标签结构的语法,编译会将JSX转成上述的js结构,上述js结构使用jsx来表示的话就是:
// 引入React和React.js的组件父类Component
import React, { Component } from 'react'
// react-dom可以帮助渲染组件到页面上
import ReactDOM from 'react-dom'
import './index.css'
class Header extends Component {
render() {
return (
React Hello!
)
}
}
// 将组件Header渲染到id为root标签中
ReactDOM.render(
,
document.getElementById("root")
)
只要写React.js组件,头部必须引入React和React.js的组件父类Component
,ReactDOM用于把 React 组件渲染到页面上,一般情况只要写组件,上述2个必须引入,述代码中在return()
中有一串纯HTML的标签,这就是JSX语法(内部的属性不再是原来html中的class
,变成了className
,因为在React中,class
是关键字,此外还有一个属性for
用htmlFor
替换),编译后变成:
import React, { Component } from 'react'
import ReactDOM from 'react-dom'
import './index.css'
class Header extends Component {
render() {
return (
React.createElement(
"div",
null
React.createElement(
"h1",
{className: 'title'},
"React Hello!"
)
)
)
}
}
ReactDOM.render(
React.createElement(Header, null),
document.getElementById("root")
)
可发现也是按照 标签–>属性–>子元素 的顺序来嵌套的,通过React.createElement(x, x)
方法创建html标签,对于JSX可以理解为js对象,会好理解点。
通过这个JSX语法可以结构化js对象,用来构造DOM元素,最后再将DOM元素塞到页面上即可,也就是上述代码中ReactDOM.render(x, x)
函数所干的事儿:渲染组件–>构造DOM树–>插入页面中特定的元素上(上例是插入到id
为root
的div
元素中),可以用下面的流程表示:
从JSX经过了JS对象才最后渲染后页面上,是因为可能会将这个JS对象渲染到canvas或手机APP上,这是把react-dom
抽出来的原因,可以想象有一个叫做react-canvas
的东西可以帮我们把UI渲染到canvas上,或者有一个叫做react-app
可以帮我们把它转成远程的APP(实际这东西叫ReactNative
)。其次,有了这样一个对象,如果数据变化,需要更新组件的时候,就可以用比较快的算法操作这个 JavaScript 对象,而不用直接操作页面上的 DOM,这样可以尽量少的减少浏览器重排,极大地优化性能。
总结
render()
方法下面这些情况可以通过之前创建的 my-app 工程来实践,直接在App.js
中修改即可。
返回的JSX形式
前面已经提过,React中写组件一般情况都要继承 React.js 的Component
类,同时也就必须与要实现一个render()
方法,这个方法必须返回一个JSX元素,但必须用一个外层JSX元素将所有内容包裹起来,如果写成多个并列JSX元素是不合法的,比如:
// 正确方式
render() {
return (
React Hello!
)
}
// 错误方式
render() {
return (
...
...
)
}
在JSX中插入表达式
在JSX中也可以使用js的表达式,变量使用{}
包裹,比如:
render() {
return (
Hello, {name}!
)
}
还可以使用{}
来计算数学表达式,如{1+3}
,最终替换为4。除此之外,还可以用{}
来包裹一个函数表达式,如{(function () { return 'is good'})()}
,最终替换为is good
,总之,{}
中可以放任何js代码(包含变量、表达式、函数。。。),render()
函数可以将这些内容的最终结果渲染到页面上,而且可以连标签的属性中也可以用,如:
render() {
return (
Hello, {name}!
)
}
之前说过,JSX中使用className
和htmlFor
替换了原来html中的class
和for
,其他的和html一样(如style
、data-*
等)。
依据条件返回
在上述插入表达式部分,React中利用{xx}
的形式可以包含多种形式的变量,除了上述的变量、表达式、函数外,还可以直接是一段 JSX 代码,如:
render() {
const isJack = true
return (
Hello,
{
isJack ? jack : stranger
}
)
}
先是在return
外定义了一个isJack
的变量,然后在JSX中又有一个{isJack ? jack : stranger}
,这里JSX会依据变量isJack
的值来选择性返回JSX的内容(简直和java中的三目运算一毛一样啊,对不对),含义也是一样的,变量为true
就将jack
渲染上去,否则就将stranger
渲染上去,注意这里是允许使用null
,就可以将内容隐藏起来(毕竟stranger
这个词听着会让人不太舒服,站在客户或用户的角度,可以直接就是Hello
,就不会让人不舒服了,如果是jack,那就显示Hello, Jack
,不是jack就直接是Hello
),改成下面的JSX:
render(){
const isJack = false
return (
{
Hello
{
isJack ? , jack ? null
}
}
)
}
除了上述对直接使用,还可以将这一块单独抽出来做一个函数,接受JSX元素作为参数,比如:
renderTest(test1, test2) {
const flag = true
return flag ? test1 : test2
}
render() {
const isJack = false
return (
{/* */}
Welcome to React,
{/* 调用定义的函数 */}
renderTest,
{
this.renderTest(
test1,
test2
)
}
);
}
组件组合(就是组件之间相互嵌套或组合)离不开自定义组件的行为,所以先记录一下自定组件的注意点:普通的 HTML 标签都用小写字母开头,而自定义组件必须要用大写字母开头,之前定义的Header
组件就是这样。下面是一个组件组合的小栗子:
// Component 已经可以直接使用
class House extends Component {
// TODO
render() {
return (
房子
// 引用组件
)
}
}
class Room extends Component {
// TODO
render() {
return (
)
}
}
class Bathroom extends Component {
// TODO
render() {
return (
浴室
)
}
}
class Man extends Component {
// TODO
render() {
return (
人
)
}
}
class Dog extends Component {
// TODO
render() {
return (
狗
)
}
}
在进行自定义组件时,像之前的Header
,死活无法引入到其他组件(我这里是App.js
组件)中,后来摸索几个小时,发现,不但要在App.js
中通过import Header from './Header';
在头部进入Header
标签,还需要Header
组件中使用export default Header
将该组件导出,综上,如果要在父组件中引用子组件,需要:
export default xxx
;import Xxx from './Xxx'
(虽然文件是Xxx.js
,但后缀可以直接省略); React对于事件的监听,需要给监听的事件加上onClick
、onKeyDown
属性即可,下面是实践的小栗子:
class Header extends Component {
{/* 这里不能添加 funcation 关键字*/}
doClick() {
console.info("React Hello is on Click!")
}
render() {
return (
{/* 这里一定需要添加 this 关键字,否则找不到 doClick */}
React Hello!
)
}
}
对于事件的监听,不需要添加浏览器原生的addEventListener
,React已经做了一系列on*
(遵照驼峰命名法)属性封装,但on*
属性有一个限制,只对HTML原生的标签有效果,对于我们自定义的标签是无法使用的(就算使用也没有效果)。
event
React会给每个事件监听传入一个event
对象(兼容所有浏览器),在函数中的参数可以随意取名调用,如:
class Header extends Component {
{/*这里直接传入event对象,至于名字可以随意取,取event或者e等等都行*/}
doClick(e) {
{/* event.target.innerHTML用于获取标签中的内容 */}
console.info(e.target.innerHTML)
}
render() {
return (
React Hello!
)
}
}
this
this
关键字指的是这个实例本身,如果在上述的doClick(e)
打印this
(即console.info(this)
),结果为undefined
,React调用我们传过去的方法(这里是指this.doClick
)时,是通过函数调用的方式,而非对象方法调用的方式,所以在函数(指doClick
)中并不能获取当前对象实例信息,除非在调用函数时使用bind
将实例绑定到当前实例,如下:
React Hello!
这样以后在doClick
函数中就可以打印出this
对象了。
bind
bind
可以用于绑定当前实例到this
上,除此外,还可以通过bind
来绑定一些参数,如:
class Header extends Component {
doClick(v1, v2) {
console.info(this, v1, v2)
}
render() {
return (
React Hello!
)
}
}
这样点击后打印出来的结果为:Header {props: {…}, context: {…}, refs: {…}, updater: {…}, _reactInternalFiber: FiberNode, …} "Hello" "React"
,然后开始作死测试各种情况:
情况1
class Header extends Component {
doClick(v1) {
console.info(this, v1)
}
render() {
return (
React Hello!
)
}
}
结果Header {props: {…}, context: {…}, refs: {…}, updater: {…}, _reactInternalFiber: FiberNode, …} "Hello"
,满足期望,拿到传入的"Hello";
情况2
class Header extends Component {
doClick(v1) {
console.info(v1)
}
render() {
return (
React Hello!
)
}
}
结果Class {dispatchConfig: {…}, _targetInst: FiberNode, nativeEvent: MouseEvent, type: "click", target: h1, …}
(这个就是event
对象);
情况3
class Header extends Component {
doClick(v1, v2) {
console.info(v1, v2)
}
render() {
return (
React Hello!
{/* */}
)
}
}
结果Recat Class {dispatchConfig: {…}, _targetInst: FiberNode, nativeEvent: MouseEvent, type: "click", target: h1, …}
;
情况4
class Header extends Component {
doClick(v1, v2) {
console.info(v1, v2)
}
render() {
return (
React Hello!
)
}
}
结果Recat test
;
情况5
class Header extends Component {
doClick(v1, v2) {
console.info(v1, v2)
}
render() {
return (
React Hello!
)
}
}
结果Recat test
;
【总结】
bind(this, ...)
,即如果bind
如果将当前实例对象绑定到this
上,并传入了一些其他参数,调用方传入3个参数,接收方只定义了2个参数,如doClick (v1, v2)
,调用方this.doClick.bind(this, 'test1', 'test2', 'test3')
,那doClick
接受到的参数只取前2个传入参数(绑定的this
不占参数数量),即v1=test1, v2=test2
;如果调用方传入2个参数,接收方却定义了3个参数,如doClick (v1, v2, v3)
、this.doClick.bind(this, 'test1', 'test2')
,那只多余的一个参数默认是event
对象,即v1=test1, v2=test2, v3=Calss {...}
,如果参数再多,比如定义了4个参数,那v4
就为undefined
了依次类推;bind('test1', 'test2' ...)
,这种情况完全找不到规律,(*  ̄︿ ̄)综合实例
有只狗,一摸它就会叫,然后就跑了,实现如下:
class Header extends Component {
bark() {
console.log('bark')
{/* 如果要调用 run 方法必须加 this */}
this.run()
}
run() {
console.log('run')
}
render() {
return (
{/* 点击Dog后先咬再跑 */}
Dog
)
}
}
注:和之前的一样,React调用我们传过去的方法是通过函数调用的方式,而非对象方法调用的方式,所以在函数中并不能获取当前对象实例信息,也不能获取到当前实例中其他方法,除非在调用函数时使用bind
将实例绑定到当前实例,所以要想在break()
中调用run()
方法,必须绑定实例对象到this
上传给break
,否则无法使用this
获取当前对象中的run
方法。
sate
组件的显示形态是由数据状态和配置参数决定的,React中使用state
来存储可变化的状态,下面是一个小栗子:
class LikeButton extends Component {
constructor() {
super()
{/*注意状态赋值的方式,用冒号*/}
this.state = { isLiked: false }
}
handleClickOnLikeButton() {
// 更新当前实例中 state 中的 isLiked 变量
this.setState({
isLiked: !this.state.isLiked
})
}
render() {
return (
)
}
}
根据按钮的状态渲染点赞按钮旁边的文字(“取消”或“点赞”),将isLiked
放到LikeButton
实例的state
中,并且在构造函数中初始化,最终会根据组件中的state
中的isLiked
的值进行不同点赞状态的渲染,将内部状态的变化绑定到button
点击监听事件中。至于不同状态的具体渲染,React在每次调用实例的setState()
方法时都会重新调用ReactDOM.render()
方法进行页面渲染。除了上述state
只放一个状态外,还可以同时放多个状态量(用逗号分隔),这种情况下如果只有部分变量变化,那在setState()
中只需要变化部分状态变量即可,不用全部放进去更新。
注:
this.setState(...)
,不能直接使用this.state=xxx
,这种方式React不能监听到组件的状态变化,因此页面元素不能得到重新渲染,改变状态一定要用setState(...)
,这个方法接受的参数可以是一个对象(上述接受的就是一个JSX对象)或者函数;setState()
并不是即时生效的,而是先将改变的状态缓存在一个队列中,然后在合适的时候再将状态改变,这一点可以通过在this.setState()
前后增加console.info(this.state.isLiked)
来查看值是一样的,中间经过setState
后再打印并没有立刻就改变状态了;setState
并不是即时生效的,但如果想基于刚刚设置的state
中的状态变量来做一些改动几乎不可能,因为通过this.state.xxx
拿到的状态是老状态,而不是刚设置的状态,如果想拿到刚设置的状态,setState()
中应该传入一个函数作为参数(React会自动将上一个setState
的结果传到这个函数中),此时便可以拿到上一个设置进去的状态(即使该状态此时可能并未生效);下面是注意点2、3的小栗子:
class LikeButton extends Component {
constructor() {
super()
this.state = { isLiked: false, count: 0 }
}
handleClickOnLikeButton() {
console.log(this.state.count)
this.setState({count: this.state.count + 1})
this.setState({count: this.state.count + 1})
console.log(this.state.count)
}
render() {
return (
)
}
}
第一次运行输出结果为0, 0
,再点击一次结果为1, 1
,但我们期望第二次运行输出结果为2, 2
,这说明第一次点击时2个this.setState({count: this.state.count + 1})
中第2个setSate
并没有生效(未生效的原因是因为this.state.count
获取的还是老状态变量,而不是状态队列中刚刚设进去的1
)。下面是利用在setSate()
中传入函数的方式获取到上次设置的状态值prevState
,如下:
// 将两次setState改成下面这样
this.setState({
count: this.state.count + 1
})
// 传入函数,React将上次设置的状态值自动传过来,取名为prevState(这个名字随意取)
this.setState((prevState) => {
return { count: prevState.count + 1 }
})
此时第一次点击结果为0, 0
,再点击一次结果为2, 2
,发现基于上一次设置的值做修改生效,这里不要和“稍后渲染”搞混,虽然是基于上次设进去值进行修改,但是打印出来还是老状态,还是稍后渲染。
setState()
渲染性能
在上述按钮监听事件中,进行了多次(栗子中是2次)setState
更新状态,虽然如此,但实际页面组件只做了一次渲染,而不是两次,React自动将js事件循环的消息队列中同一个消息中的setState
进行合并后再重新渲染,这意味着在搞东西时,多次调用setState
并不会导致性能下降。这和上面的“React在每次调用实例的setState()
方法时都会重新调用ReactDOM.render()
方法进行页面渲染”并不矛盾,只是将多次渲染合并在一起而已。
props
这个就是组件的属性,由于组件是可以不断可以进行复用的,那就希望组件的某些属性不要太死,否则不能进行有效的复用,props
指的组件的可配置性,可以根据不同的场景进行配置,每个组件都可以有一个props
参数,包含了这个组件中所有定义的组件配置,具体使用如下:
// 1. 定义一个点赞组件,可以自定义说明文本
import React, { Component } from 'react';
import ReactDOM from 'react-dom';
class LikeButtonPlus extends Component {
constructor() {
super()
this.state = { isLiked: false }
}
handleClickOnLikeButton() {
this.setState({
isLiked: !this.state.isLiked
})
}
render() {
// 定义取消已赞状态的说明文本变量
const likedText = this.props.likedText || "取消"
// 定义未点赞状态的说明文本变量
const unlikedText = this.props.unlikedText || "点赞"
return (
)
}
}
export default LikeButtonPlus
// 2. 在其他组件中使用,传入已定义的属性 props: likedText和unlike
使用的时候,直接将参数放在自定义组件标签中即可,所有的属性都会作为props
对象的键值,这样就可以对按钮状态说明文本进行自定义,最终效果为:,组件中参数可以是任意类型的数据(字符串、数字、对象、函数…),下面是传入一个数组对象调用:
// 按钮组件中render中渲染语句
const text = this.props.text || {likedText: '取消', unlikedText: '点赞'}
return (
)
// 在其他组件中使用,直接传入一个数组对象text
更厉害的是,还可以通过props
传递函数,如:
// 组件中通过 props 获取传递的函数并执行
handleClickOnLikeButton() {
this.setState({
isLiked: !this.state.isLiked
})
// 如果父组件中 onClick2 函数不为空就执行传过来的函数
if (this.props.onClick2) {
this.props.onClick2()
}
}
// 父组件中传入函数
console.info('Click on like button!')} />
defaultProps
在上述使用props
时,是先定义了两个变量(或者包含这两个变量的对象)likedText
和unlikedText
,就是在具体渲染DOM时,如果父类组件中在组件标签中给了对应的props
属性,就使用父组件中给的属性,否则使用子类组件中设置的,即const likedText = this.props.likedText || "取消"
是做了一个逻辑或操作,如果属性很多,我们需要作很多逻辑或的操作,使用效果不佳,由此 React 提供了defaultProps
来对组件进行可配置化组件属性进行默认配置(直接用this.props...
),示例代码如下:
class LikeButtonPlus extends Component {
// 定义默认可配置属性,React 会自动应用,不用手动调用
static defaultProps = {
text: {
likedText: '取消',
unlikedText: '点赞'
}
}
...
render() {
return (
)
}
}
props
的不可变性 通俗点说,就是父组件通过标签给子组件传入相应的props
属性,这个props
一旦传入子组件后就不能改变了,如果子组件尝试对自身props
作修改将出错,比如:
// 父组件调用部分
// 子组件修改部分
handleClickOnLikeButton() {
// 尝试修改
this.props.text = {
likedText: '取消2',
unlikedText: '点赞2'
}
this.setState({
isLiked: !this.state.isLiked
})
}
render() {
return (
)
}
根据上述的报错信息可以看到第18行代码报错,提示不能对只读属性text
(即props
属性)赋值。
待解决
但是在作死的这条路上从没有停过,我试着在子类组件中这样去修改:
// 子组件修改部分
handleClickOnLikeButton() {
// 尝试修改
// this.props.text = {
// likedText: '取消2',
// unlikedText: '点赞2'
// }
this.props.text.likedText = '取消2'
this.props.text.unlikedText = '点赞2'
this.setState({
isLiked: !this.state.isLiked
})
}
render() {
return (
)
}
然后,是的,修改成功了o((⊙﹏⊙))o.,还挺尴尬的,效果如下:
抛开上述的插曲,React中子类组件对于父类组件传过来的props
是不能改变的,React希望子组件在输入确定的props
属性后,能够输出确定的UI显示形态,如果在输入props
属性到输出UI形态中间,props
再被修改,这种行为会导致具体页面渲染形态不可预知,所以 React 是禁止这种中间行为来修改子组件的属性。但已经输入的props
属性还是有途径修改的:通过组件调用者(父组件)主动重新渲染DOM可以达到目标,示例如下:
// 1. 父组件
class App extends Component {
constructor() {
super()
this.state = {
text: {
likedText: 'unliked',
unlikedText: 'liked'
}
}
}
handleClickOnChange() {
this.setState({
text: {
likedText: '取消2',
unlikedText: '点赞2'
}
})
}
render() {
return (
测试修改props
);
}
}
子类组件中不进行任何props
的修改行为,在原先组件标签中赋值的props
属性为unliked, unliked
,但中间可以通过按钮“修改props”修改已经传给子组件的props
属性,效果如下:
父类组件比原来多了构造器(主要是为了引入state
状态量),然后将需要传入子组件中的props
塞到这里面,这样么干的目的是为了在修改props
中的text
属性能够让APP组件够主动去调render()
方法主动重新渲染页面,父组件重新渲染时子组件LikeButtonPlus
也会收到通知需要重新渲染,从而到达改变props
的目的。
state
和props
的区别 state
和props
有点相似,正常情况下,如果一个组件中没有设置state
叫“无状态”组件,反之,如果组件中设置了state
叫“状态”组件,因为状态管理相对比较复杂,所以实际过程中尽量避免“状态”组件的定义,这个理念也是React所提倡的(鼓励“无状态”组件)。下面记录一下state
和props
的具体区别:
state
:在组件内部的构造体中进行初始化,仅用于组件管理自身的可变状态,外部组件不能访问、修改,自身可通过this.setState
进行状态更新(setState
会导致当前组件的重新渲染);props
:在子类组件进行定义,调用方作为父组件可以在子组件标签中传入props
属性以控制子组件相关属性,子组件无法控制、修改它自己的props
属性(只能作类似于初始化的操作defaultProps
),只能通过调用方组件传入props
来控制,否则自身的props
不变。上述所有的组件都是一个组件就是一个类的组织方式,函数组件则是一个函数一个组件(有点颠覆),比如:
render() {
// 定义一个函数组件
const FunctionComponent = (props) => {
const fc = (event) => alert('This is a Function Component!')
return (
函数组件
)
}
return (
{/*调用函数组件*/}
);
}
函数组件的引用和之前那种继承Component
类是一样,还是通过
的形式引用,函数组件支持props
、可以提供render
方法,但不支持设置state
。
数组的渲染
下面是一个数组的渲染简单示例:
列表渲染
{[
test1,
test2,
test3
]}
之前说过{xx}
可以放任意对象,当然也可以放数组对象(即[...]
中的内容),React 会自动将数组中的每个元素在页面上罗列渲染,效果如下:
使用for
循环渲染数组数据
通过上述数组的渲染方式,具体数据的渲染示例代码如下:
// 定义数组
const users = [
{ username: 'Jerry', age: 21, gender: 'male' },
{ username: 'Tomy', age: 22, gender: 'male' },
{ username: 'Lily', age: 19, gender: 'female' },
{ username: 'Lucy', age: 20, gender: 'female' }
]
class App extends Component {
render() {
// 定义每个用户渲染后的JSX的数组,每个元素中存放的是已带标签的jsx元素
const usersElements = []
// 循环数组中的每个用户,将每个属性 push 到数组中
for (let user of users) {
usersElements.push(
{/* 获取每个用户中三个属性 */}
name: {user.username}
gender: {user.gender}
age: {user.age}
)
}
return (
使用map渲染数据
{/*将每个用户的属性数组塞进去*/}
{usersElements}
);
}
}
最终的效果如下:
上述过程是手动取出数组中的各个对象的属性,然后再用封装成一个包含对应个数的jsx数组,然后将这个数组交给另一个jsx去渲染,最后一步就是{xx}
,xx
是数组,React自动将数组中的每个元素在页面上罗列渲染。总而言之,上述的数组数据的渲染被分裂成了2个步骤:
但实际,2个步骤是可以合并的,ES6中的map
函数(和java
中有点像)可以很轻松的遍历到数组中每个元素,代码如下:
使用map渲染自动渲染
{
users.map((user) => {
return (
name: {user.username}
age: {user.age}
gender: {user.gender}
)
})
}
抽出数组中的每个元素独立成为组件
上述使用map
函数来自动遍历效果还不错,但为了结构上的清晰,想把每个数组中的“用户”这个元素的结构进一步抽出来作为一个单独的组件,意思就是把上述使用for
循环渲染数组数据中提到的:
{/* 获取每个用户中三个属性 */}
name: {user.username}
gender: {user.gender}
age: {user.age}
就是这个东西抽离出来,因为这里面覆盖了一个“用户”中所有的属性,把这些属性单独提出来就是一个结构鲜明的“User”对象(有点像JavaBean),单独定一个结构已确定的User
组件:
class User extends Component {
render() {
const {user} = this.props
return (
name: {user.username}
gender: {user.gender}
age: {user.age}
)
}
}
export default User
然后在父组件中调用时,只要给定user
这个props
即可将数据渲染到User
组件上:
const users = [
{ username: 'Jerry', age: 21, gender: 'male' },
{ username: 'Tomy', age: 22, gender: 'male' },
{ username: 'Lily', age: 19, gender: 'female' },
{ username: 'Lucy', age: 20, gender: 'female' }
]
class App extends Component {
render() {
return (
map渲染每个抽离的组件
{
users.map((user) => {
return (
)
})
}
);
}
}
map
的作用仅仅是为了取出数组中每个“用户”,然后将每个用户通过
标签传入user
属性,即可将该用户相应的三个属性渲染到User
组件上。这样搞完,数组数据确实渲染上去了,但是控制台通常会有如下的错误信息:
Warning: Each child in an array or iterator should have a unique "key" prop.
即提示数组遍历的每个
元素应该有一个唯一键值(称之为key
的一个prop
)
【附】
项目地址React_primary