仅供个人学习!
本文作者:胡子大哈
本文原文:http://huziketang.com/books/react/blog/lesson4
注意: setTimeout(code,millisec)
setTimeout() 方法用于在指定的毫秒数后调用函数或计算表达式。
code | 必需。要调用的函数后要执行的 JavaScript 代码串。 |
millisec | 必需。在执行代码前需等待的毫秒数。 |
必须要用一个外层的 JSX 元素把所有内容包裹起来。返回并列多个 JSX 元素是不合法的
举例:错误的做法:
render () {
return (
<div>第一个</div>
<div>第二个</div>
)
}
...
正确的是:
<div>
<div>第一个</div>
<div>第二个</div>
</div>
在 JSX 当中你可以插入 JavaScript 的表达式,表达式返回的结果会相应地渲染到页面上。表达式用 {}
包裹。
举例:
render () {
const word = 'is good'
return (
<div>
<h1>React 小书 {word}</h1>
</div>
)
}
改成
{1 + 2}或
{(function () { return 'is good'})()}
总之:{}
内可以放任何 JavaScript 的代码,包括变量、表达式计算、函数执行等等。 render
会把这些代码返回的内容如实地渲染到页面上
这点也可以用在属性里
注意:直接使用 class
在 React.js 的元素上添加类名如 <div class=“xxx”>
这种方式是不合法的。因为 class
是 JavaScript 的关键字,所以 React.js 中定义了一种新的方式:className
来帮助我们给元素添加类名。
还有一个特例就是 for
属性,例如 <label for='male'>Male</label>
,因为 for
也是 JavaScript 的关键字,所以在 JSX 用 htmlFor
替代,即 <label htmlFor='male'>Male</label>
。而其他的 HTML 属性例如 style
、data-*
等就可以像普通的 HTML 属性那样直接添加上去。
结合一下,我们可以做到显示或者隐藏某些元素:
例:
render () {
const isGoodWord = true
return (
<div>
<h1>
React 小书
{isGoodWord
? <strong> is good</strong>
: null//根据值的不同可能显示可能隐藏
}
</h1>
</div>
)
}
...
————————————也可以有个升级版:
...
render () {
const isGoodWord = true
const goodWord = <strong> is good</strong>
const badWord = <span> is not good</span>
return (
<div>
<h1>
React 小书
{isGoodWord ? goodWord : badWord}
</h1>
</div>
)
}
...
——————这个有点难理解
...
renderGoodWord (goodWord, badWord) {
const isGoodWord = true
return isGoodWord ? goodWord : badWord
}
render () {
return (
<div>
<h1>
React 小书
{this.renderGoodWord(
<strong> is good</strong>,
<span> is not good</span>//有点像解构赋值那种 只不过赋的是组件
)}
</h1>
</div>
)
}
...
组件的组合、嵌套和组件树
注意:组件标签也是要关闭的 <Title />
这样可复用性非常强,我们可以把组件的内容封装好,然后灵活在使用在任何组件内。
自定义的组件都必须要用大写字母开头,普通的 HTML 标签都用小写字母开头。
例:
<div>
<Title />
<Title />
<Title />
</div>
假设页面是由 Header
、Main
、Footer
几个部分组成,由一个 Index
把它们组合起来。
层层包裹的关系
class Title extends Component {
render () {
return (
<h1>React 小书</h1>
)
}
}
class Header extends Component {
render () {
return (
<div>
<Title />
<h2>This is Header</h2>
</div>
)
}
}
class Main extends Component {
render () {
return (
<div>
<h2>This is main content</h2>
</div>
)
}
}
class Footer extends Component {
render () {
return (
<div>
<h2>This is footer</h2>
</div>
)
}
}
class Index extends Component {
render () {
return (
<div>
<Header />
<Main />
<Footer />
</div>
)
}
}
ReactDOM.render(
<Index />,
document.getElementById('root')
)
事件监听
class Title extends Component {
handleClickOnTitle () {
console.log('Click on title.')
}
render () {
return (
<h1 onClick={this.handleClickOnTitle}>React 小书</h1>
//this.***指向一个组件
)
}
}
在 React.js 不需要手动调用浏览器原生的 addEventListener
进行事件监听。React.js 帮我们封装好了一系列的 on*
的属性,当你需要为某个元素监听某个事件的时候,只需要简单地给它加上 on*
就可以了。而且你不需要考虑不同浏览器兼容性的问题,React.js 都帮我们封装好这些细节了。
https://reactjs.org/docs/events.html#supported-events 还有很多现成事件 这些事件属性名都必须要用驼峰命名法
这些 on*
的事件监听只能用在普通的 HTML 的标签上,而不能用在组件标签上。以后会学如何绑定起来用到组件上
关于事件中的 this
一般在某个类的实例方法里面的 this
指的是这个实例本身。但是你在上面的 handleClickOnTitle
中把 this
打印出来,你会看到 this
是 null
或者 undefined
。
这是因为 React.js 调用你所传给它的方法的时候,并不是通过对象方法的方式调用(this.handleClickOnTitle
),而是直接通过函数调用 (handleClickOnTitle
),所以事件监听函数内并不能通过 this
获取到实例。
如果你想在事件函数当中使用当前的实例,你需要手动地将实例方法 bind
到当前实例上再传入给 React.js。
//改成以下形式
render () {
return (
<h1 onClick={this.handleClickOnTitle.bind(this)}>React 小书</h1>
)
}
}
bind
不仅可以帮我们把事件监听方法中的 this
绑定到当前组件实例上;还可以帮助我们在在渲染列表元素的时候,把列表元素传入事件监听函数当中
组件的 state 和 setState
注意,当我们要改变组件的状态的时候,不能直接用 this.state = xxx
这种方式来修改,如果这样做 React.js 就没办法知道你修改了组件的状态,它也就没有办法更新页面。所以,一定要使用 React.js 提供的 setState
方法,它接受一个对象或者函数作为参数。
当你调用 setState
的时候,React.js 并不会马上修改 state。例:
...
handleClickOnLikeButton () {
console.log(this.state.isLiked)
this.setState({
isLiked: !this.state.isLiked
})
console.log(this.state.isLiked)
}
...
你会发现两次打印的都是 false
,即使我们中间已经 setState
过一次了。这并不是什么 bug,只是 React.js 的 setState
把你的传进来的状态缓存起来,稍后才会帮你更新到 state
上,所以你获取到的还是原来的 isLiked
。
所以我们想要一种能实时更新的方法:
这里就自然地引出了
setState
的第二种使用方式,可以接受一个函数作为参数。React.js 会把上一个setState
的结果传入这个函数,你就可以使用该结果进行运算、操作,然后返回一个对象作为更新state
的对象:例:
... handleClickOnLikeButton () { this.setState((prevState) => { return { count: 0 } }) this.setState((prevState) => { return { count: prevState.count + 1 } // 上一个 setState 的返回是 count 为 0,当前返回 1 }) this.setState((prevState) => { return { count: prevState.count + 2 } // 上一个 setState 的返回是 count 为 1,当前返回 3 }) // 最后的结果是 this.state.count 为 3 } ...
【也就是设置一个变量】
在使用 React.js 的时候,并不需要担心多次进行 setState
会带来性能问题。
配置组件的 props
怎么把 props
传进去呢?在使用一个组件的时候,可以把参数放在标签的属性当中,所有的属性都会作为 props
对象的键值:
例:
class Index extends Component {
render () {
return (
<div>
<LikeButton likedText='已赞' unlikedText='赞' />
</div>
)
}
}
现在我们把 likedText
和 unlikedText
这两个参数封装到一个叫 wordings
的对象参数内,然后传入点赞组件中。
例:
class Index extends Component {
render () {
return (
<div>
<LikeButton wordings={{likedText: '已赞', unlikedText: '赞'}} />
</div>
)
}
}
{{}}
就是在 {}
内部用对象字面量返回一个对象而已。
class LikeButton extends Component {
constructor () {
super()
this.state = { isLiked: false }
}
handleClickOnLikeButton () {
this.setState({
isLiked: !this.state.isLiked
})
}
render () {
const wordings = this.props.wordings || { // ||实现默认配置
likedText: '取消',
unlikedText: '点赞'
}
return (
<button onClick={this.handleClickOnLikeButton.bind(this)}>
{this.state.isLiked ? wordings.likedText : wordings.unlikedText} ?
</button>
)
}
}
甚至可以往组件内部传入函数作为参数:
class Index extends Component {
render () {
return (
<div>
<LikeButton
wordings={{likedText: '已赞', unlikedText: '赞'}}
onClick={() => console.log('Click on like button!')}/>
</div>
)
}
}
这样可以通过 this.props.onClick
获取到这个传进去的函数,
...
handleClickOnLikeButton () {
this.setState({
isLiked: !this.state.isLiked
})
if (this.props.onClick) {
this.props.onClick()
}
}
...
一个组件的行为、显示形态都可以用 props
来控制,就可以达到很好的可配置性。
默认配置 defaultProps
defaultProps
作为点赞按钮组件的类属性,里面是对 props
中各个属性的默认配置。
这样我们就不需要判断配置属性是否传进来了:如果没有传进来,会直接使用 defaultProps
中的默认属性。
所以可以看到,在 render
函数中,我们会直接使用 this.props
而不需要再做判断。
class LikeButton extends Component {
static defaultProps = {
likedText: '取消',
unlikedText: '点赞'
}
constructor () {
super()
this.state = { isLiked: false }
}
handleClickOnLikeButton () {
this.setState({
isLiked: !this.state.isLiked
})
}
render () {
return (
<button onClick={this.handleClickOnLikeButton.bind(this)}>
{this.state.isLiked
? this.props.likedText
: this.props.unlikedText} ?
</button>
)
}
}
props 不可变
props
一旦传入进来就不能改变。
你不能改变一个组件被渲染的时候传进来的 props
。
但这并不意味着由 props
决定的显示形态不能被修改。组件的使用者可以主动地通过重新渲染的方式把新的 props
传入组件当中。
由于 setState
会导致 Index
重新渲染,达到更新状态的效果。
class Index extends Component {
constructor () {
super()
this.state = {
likedText: '已赞',
unlikedText: '赞'
}
}
handleClickOnChange () {
this.setState({
likedText: '取消',
unlikedText: '点赞'
})
}
render () {
return (
<div>
<LikeButton
likedText={this.state.likedText}
unlikedText={this.state.unlikedText} />
<div>
<button onClick={this.handleClickOnChange.bind(this)}>
修改 wordings
</button>
</div>
</div>
)
}
}
题目:
完成两个组件,电脑 Computer
和显示器 Screen
。
电脑有个 status
状态表示电脑现在是开还是关的,status
为 on
为开,status
为 off
为关,默认状态为 off
。电脑有个按钮,点击可以自由切换电脑的开关状态。
显示器接受一个名为 showContent
的 props
,显示器会把它内容显示出来。如果不传入 showContent
,显示器显示 “无内容”。
电脑包含显示器,当电脑状态为开的时候显示器显示“显示器亮了”,否则显示“显示器关了”。
export default class Computer extends Component {
constructor () {//构造函数之绑定一下
super()
this.state = { status : 'off' }//设置初始状态
}
handleClickOnButton(){//点击的函数
this.setState({
status:this.state.status==='off'?'on':'off'//状态改变
});
}
render () {
return (
<div>//渲染组件
<Screen showContent={this.state.status==='on'?'显示器亮了':'显示器关了'}/>
//自定义组件 驼峰命名 Screen 并且改变showcontent参数
<button onClick={this.handleClickOnButton.bind(this)}>开关</button>//开关组件
</div>
)
}
}
class Screen extends Component {
static defaultProps = {//设置默认参数
showContent : '无内容'
}
render () {
return (
<div className='screen'>{this.props.showContent}</div>//渲染一个组件 显示参数
)
}
}
关于 state
和 props
的总结。
state
的主要作用是用于组件保存、控制、修改自己的可变状态。state
在组件内部初始化,可以被组件自身修改,而外部不能访问也不能修改。你可以认为 state
是一个局部的、只能被组件自身控制的数据源。state
中状态可以通过 this.setState
方法进行更新,setState
会导致组件的重新渲染。
props
的主要作用是让使用该组件的父组件可以传入参数来配置该组件。它是外部传进来的配置参数,组件内部无法控制也无法修改。除非外部组件主动传入新的 props
,否则组件的 props
永远保持不变。
state
是让组件控制自己的状态,props
是让外部对组件自己进行配置。
尽量少地用 state
,尽量多地用 props
。
函数式组件就是一种只能接受 props
和提供 render
方法的类组件。这个不建议使用。。。
渲染列表数据
有一个用户列表数据,
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' }
]
之前说过 JSX 的表达式插入 {}
里面可以放任何数据,如果我们往 {}
里面放一个存放 JSX 元素的数组会怎么样?
如果你往 {}
放一个数组,React.js 会帮你把数组里面一个个元素罗列并且渲染出来。
render () {
return (
<div>
{[
<span>React.js </span>,
<span>is </span>,
<span>good</span>
]}
</div>
)
}
用循环把元素渲染到页面上:循环上面用户数组里面的每一个用户,为每个用户数据构建一个 JSX,然后把 JSX 放到一个新的数组里面,再把新的数组插入 render
方法的 JSX 里面
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 Index extends Component {
render () {
const usersElements = [] // 保存每个用户渲染以后 JSX 的数组
for (let user of users) {
usersElements.push( // 循环每个用户,构建 JSX,push 到数组中
<div>
<div>姓名:{user.username}</div>
<div>年龄:{user.age}</div>
<div>性别:{user.gender}</div>
<hr />
</div>
)
}
return (
<div>{usersElements}</div>
)
}
}
ReactDOM.render(
<Index />,
document.getElementById('root')
)
这里用了一个新的数组 usersElements
,然后循环 users
数组,为每个 user
构建一个 JSX 结构,然后 push 到 usersElements
中。然后直接用表达式插入,把这个 userElements
插到 return 的 JSX 当中。因为 React.js 会自动化帮我们把数组当中的 JSX 罗列渲染出来,所以可以看到页面上显示
但我们一般不会手动写循环来构建列表的 JSX 结构,可以直接用 ES6 自带的 map
class Index extends Component {
render () {
return (
<div>
{users.map((user) => {//.map()遍历
return (
<div>
<div>姓名:{user.username}</div>
<div>年龄:{user.age}</div>
<div>性别:{user.gender}</div>
<hr />
</div>
)
})}
</div>
)
}
}
现在进一步把渲染单独一个用户的结构抽离出来作为一个组件,继续优化代码:
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 User extends Component {
render () {
const { user } = this.props
return (
<div>
<div>姓名:{user.username}</div>
<div>年龄:{user.age}</div>
<div>性别:{user.gender}</div>
<hr />
</div>
)
}
}
class Index extends Component {
render () {
return (
<div>
{users.map((user) => <User user={user} />)}//js写在标签里要用{}括起来
</div>
)
}
}
ReactDOM.render(
<Index />,
document.getElementById('root')//作用:渲染到页面上 root是index.html里的
)
负责展示用户数据的 JSX 结构抽离成一个组件 User
,并且通过 props
把 user
数据作为组件的配置参数传进去;这样改写 Index
就非常清晰了,看一眼就知道负责渲染 users
列表,而用的组件是 User
。
key
是处理列表元素的复用性会有一个问题:元素可能会在一个列表中改变位置。
<div>a</div>
<div>b</div>
<div>c</div>
//变成
<div>a</div>
<div>c</div>
<div>b</div>
c
和 b
的位置互换了。但其实 React.js 只需要交换一下 DOM 位置就行了,但是它并不知道其实我们只是改变了元素的位置,但如果给每个元素加上唯一的标识,React.js 就可以知道这两个元素只是交换了位置:这时候就用到key了
<div key='a'>a</div>
<div key='b'>b</div>
<div key='c'>c</div>
对于用表达式套数组罗列到页面上的元素,都要为每个元素加上 key
属性,这个 key
必须是每个元素唯一的标识。一般来说,key
的值可以直接后台数据返回的 id
,因为后台的 id
都是唯一的。
这时候可以利用循环赋key值
return (
<div>
{users.map((user, i) => <User key={i} user={user} />)}
</div>
记住一点:在实际项目当中,如果你的数据顺序可能发生变化,标准做法是最好是后台数据返回的 id
作为列表元素的 key
。