react核心基础知识的总结以及学习过程中认识的一些小技巧的记录
jsx是JavaScript 的语法扩展,写法上是HTML和JavaScript参杂着写,编译之后也就是JavaScript 对象。
一般写法:
import React, { Component } from 'react'
import ReactDOM from 'react-dom'
import './index.css'
class Header extends Component {
render () {
return (
<div>
<h1 className='title'>React 小书</h1>
</div>
)
}
}
ReactDOM.render(
<Header />,
document.getElementById('root')
)
经过编译之后:
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 小书"
)
)
)
}
}
ReactDOM.render(
React.createElement(Header, null),
document.getElementById('root')
);
与其他框架监听数据动态改变dom不同,react采用setState来控制视图的更新。setState会自动调用render函数,触发视图的重新渲染,如果仅仅只是state数据的变化而没有调用setState,并不会触发更新。
render方法必须要返回一个jsx元素且只包含一个根元素。
错误的写法:
...
render () {
return (
<div>第一个</div>
<div>第二个</div>
)
}
...
正确的写法:
...
render () {
return (
<div>
<div>第一个</div>
<div>第二个</div>
</div>
)
}
...
在jsx中可插入JavaScript的表达式,表达式返回的结果会相应的渲染到页面上。表达式需要用 { }
包裹住。
...
render () {
const word = 'is good'
return (
<div>
<h1>React 小书 {word}</h1>
</div>
)
}
...
这里在页面上渲染出来的会是" React 小书 is good "。还可以在{ }
中进行计算、判断以及函数表达式的编写等。
控制一个元素的显示与隐藏:
...
render () {
const isGoodWord = true
return (
<div>
<h1>
React 小书
{isGoodWord
? <strong> is good</strong>
: null
}
</h1>
</div>
)
}
...
当条件为真时,会渲染出标签里面的内容, 为假时则返回null,也就是隐藏掉
标签里面的内容。
传入一个函数:
...
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>
)
}
...
组件是react的核心内容之一, 对于页面上重复出现可以复用的代码可以把它们提取出来封装成为通用组件进行复用。组件分为两类:有状态组件和无状态组件,也就是类组件和函数组件。
类组件:
class Welcome extends React.Component {
render() {
return <h1>Hello, {this.props.name}</h1>;
}
}
函数组件:
function Welcome(props) {
return <h1>Hello, {props.name}</h1>;
}
这里要注意的是,自定义的组件都必须要用大写字母开头,普通的 HTML 标签都用小写字母开头。
在 React.js 里面监听事件是很容易的事情,你只需要给需要监听事件的元素加上属性类似于 onClick
、onKeyDown
这样的属性,例如我们现在要给 一个标签加上点击的事件监听:
class Index extends Component {
handleClick () {
console.log('Click.')
}
render () {
return (
<h1 onClick={this.handleClick}>React 小书</h1>
)
}
}
当用户点击 h1 的时候,React.js 就会调用这个方法,所以你在控制台就可以看到 Click. 打印出来。
在 React.js 不需要手动调用浏览器原生的 addEventListener 进行事件监听。React.js 已经封装好了一系列的 on* 的属性,当你需要为某个元素监听某个事件的时候,只需要简单地给它加上 on* 就可以了。
没有经过特殊处理的话,这些 on* 的事件监听只能用在普通的 HTML 的标签上,而不能用在组件标签上。也就是说,<自定义组件 onClick={…} />
这样的写法并不会有任何的效果。可以通过别的方法来实现这样的绑定。
一般在某个类的实例方法里面的 this
指的是这个实例本身。但是你在上面的 handleClick
中把 this
打印出来,你会看到 this
是 null
或者 undefined
。
...
handleClick (e) {
console.log(this) // => null or undefined
}
...
这是因为 React.js 调用你所传给它的方法的时候,并不是通过对象方法的方式调用(this.handleClick
),而是直接通过函数调用 (handleClick
),所以事件监听函数内并不能通过 this
获取到实例。
如果你想在事件函数当中使用当前的实例,你需要手动地将实例方法 bind
到当前实例上再传入给 React.js。
class Index extends Component {
handleClick () {
console.log('Click.')
}
render () {
return (
<h1 onClick={this.handleClick.bind(this)}>React 小书</h1>
)
}
}
bind
会把实例方法绑定到当前实例上,然后我们再把绑定后的函数传给 React.js 的 onClick
事件监听。
还可以在 bind
的时候给事件监听函数传入一些参数:
class Index extends Component {
handleClick (word) {
console.log(word)
console.log(this)
}
render () {
return (
<h1 onClick={this.handleClick.bind(this, 'Hello')}>React 小书</h1>
)
}
}
一个组件可以拥有自己的状态state, 不过仅限于类组件, 函数组件并没有state, 这就是区别有状态组件和无状态组件的原因
```javascript
class Index extends Component {
constructor () {
super()
this.state = { bool: false }
}
handleClick () {
console.log('Click.')
}
render () {
return (
<h1 onClick={this.handleClick}>React 小书</h1>
)
}
}
可以在上面代码中的state对象里面定义多个变量类型,并对其值进行初始化bool: false
。这个组件的 render
函数内,会根据组件的 state
的中的bool
的变化来重新渲染页面。
state
只能通过setState
方法来修改里面的变量数据,而不能通过 this.state.xx = xx
来修改,如果这样做 React.js 就没办法知道你修改了组件的状态,它也就没有办法更新页面。
setState
方法由父类 Component
所提供。当我们调用这个函数的时候,React.js 会更新组件的状态 state
,并且重新调用 render
方法,然后再把 render
方法所渲染的最新的内容显示到页面上。
setState
的参数可以是对象也可以是函数,如果传入的是对象,那么只需要把需要修改的变量放在对象中然后进行修改就好:
...
constructor () {
super()
this.state = {
name: 'Tomy',
bool: false
}
}
handleClick () {
this.setState({
bool: !this.state.bool
})
}
...
这里还有要注意的是,当你调用 setState
的时候,React.js 并不会马上修改 state
。而是把这个对象放到一个更新队列里面,稍后才会从队列当中把新的状态提取出来合并到 state
当中,然后再触发组件更新。这一点要好好注意。可以体会一下下面的代码:
...
handleClickOnLikeButton () {
console.log(this.state.bool)
this.setState({
bool: !this.state.bool
})
console.log(this.state.bool)
}
...
你会发现两次打印的都是 false
,即使我们中间已经 setState
过一次了。这并不是什么 bug,只是 React.js 的 setState
把你的传进来的状态缓存起来,稍后才会帮你更新到 state
上,所以你获取到的还是原来的 bool
。
所以如果你想在 setState
之后使用新的 state
来做后续运算就做不到了,例如:
...
handleClick () {
this.setState({ count: 0 }) // => this.state.count 还是 undefined
this.setState({ count: this.state.count + 1}) // => undefined + 1 = NaN
this.setState({ count: this.state.count + 2}) // => NaN + 2 = NaN
}
...
这里就自然地引出了 setState
的第二种使用方式,可以接受一个函数作为参数。React.js 会把上一个 setState
的结果传入这个函数,你就可以使用该结果进行运算、操作,然后返回一个对象作为更新 state
的对象:
...
handleClick () {
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
}
...
上面虽然进行了三次 setState
,但是实际上组件只会重新渲染一次,而不是三次;这是因为在 React.js 内部会把 JavaScript 事件循环中的消息队列的同一个消息中的 setState
都进行合并以后再重新渲染组件。
组件是相互独立、可复用的单元,每一个组件都可以接受一个props参数,它是一个对象,包含了所有你对这个组件的配置,我们可以通过使用props把相互独立的组件关联起来,也就是组件间的通信。
class Main extends Component {
render() {
const text = this.props.text || '默认文字';
return (
<div>
<h2>This is main content{text}</h2>
</div>
)
}
}
从 render
函数可以看出来,组件内部是通过 this.props
的方式获取到组件的参数的,如果 this.props
里面有需要的属性我们就采用相应的属性,没有的话就用默认的属性。
接下来只需要在使用组件的时候把参数放在标签的属性当中,所有的属性都会作为props
对象的键值。
就像在用普通的 HTML 标签的属性一样,可以把参数放在表示组件的标签上,组件内部就可以通过 this.props
来访问到这些配置参数了。
当需要传入多个参数时,每一项都写为标签的一个属性,这样看起来代码会比较冗余,可以通过传递对象或者数组的方式, 把多个参数传递到子组件。
父组件传递:
class Index extends Component {
render() {
const obj = {
name: 'tom',
age: '18',
gender: 'male',
}
return (
<div>
<Header />
<Main {...obj} />
<Footer />
</div>
)
}
}
子组件接收:
class Main extends Component {
render() {
console.log(this.props, '<-this.props->');
const name = this.props.name || '默认文字';
const gender = this.props.gender || '默认文字';
return (
<div>
<h2>This is main content ---- {name}</h2>
<h2>This is main content ---- {gender}</h2>
</div>
)
}
}
如果使用的是无状态子组件则可以这么写
子组件:
export default function Fun(props) {
return (
<div>
123{props.num}
</div>
)
}
父组件:
render() {
return (
<div>
<Header />
<Main {...this.state.obj} onClick={this.handleClick.bind(this)} />
<Footer />
<Fun num={4565} />
</div>
)
}
还可以把函数作为参数传递,在父组件中定义一个函数并传递到子组件中:
class Index extends Component {
handleClick() {
console.log('父组件函数方法执行', '<-->');
}
render() {
const obj = {
name: 'tom',
gender: 'male',
}
return (
<div>
<Header />
<Main {...obj} onClick={this.handleClick} />
<Footer />
</div>
)
}
}
在子组件中接收函数参数并执行:
class Main extends Component {
handleClick() {
if (this.props.onClick) {
this.props.onClick();
}
}
render() {
const name = this.props.name;
const gender = this.props.gender;
return (
<div>
<h2>This is main content ---- {name}</h2>
<h2>This is main content ---- {gender}</h2>
{/* 定义一个点击事件 */}
<button onClick={this.handleClick.bind(this)}>子组件按钮</button>
</div>
)
}
}
父组件:
ReactDOM.render(
<Card content={
<div>
<h2>React.js 小书</h2>
<div>开源、免费、专业、简单</div>
订阅:<input />
</div>
} />,
document.getElementById('root')
)
子组件:
render () {
return (
<div className='card'>
<div className='card-content'>
{this.props.content}
</div>
</div>
)
}
想要实现上面的代码还可以有另一种写法,就是直接在组件之间写入HTML内容,然后通过.children来获取到。
父组件:
ReactDOM.render(
<Card>
<h2>React.js 小书</h2>
<div>开源、免费、专业、简单</div>
订阅:<input />
</Card>,
document.getElementById('root')
)
子组件:
render () {
return (
<div className='card'>
<div className='card-content'>
{this.props.children}
</div>
</div>
)
}
上面的组件默认配置我们是通过 || 操作符来实现。这种需要默认配置的情况在 React.js 中非常常见,所以 React.js 也提供了一种方式 defaultProps
,可以方便的做到默认配置。
class Main extends Component {
static defaultProps = {
name: 'emy',
gender: 'female'
}
render() {
const name = this.props.name;
const gender = this.props.gender;
return (
<div>
<h2>This is main content ---- {name}</h2>
<h2>This is main content ---- {gender}</h2>
</div>
)
}
}
defaultProps
作为组件的类属性,如果父组件没有传进来props,会直接使用 defaultProps 中的默认属性。
props
一旦传入进来就不能改变。修改上面的例子中的obj
对象。
class Main extends Component {
static defaultProps = {
name: 'emy',
gender: 'female'
}
handleClick() {
this.props.gender = 'female'
}
render() {
const name = this.props.name;
const gender = this.props.gender;
return (
<div>
<h2>This is main content ---- {name}</h2>
<h2>This is main content ---- {gender}</h2>
<button onClick={this.handleClick.bind(this)}>点击按钮</button>
</div>
)
}
}
报错:
你不能直接改变一个组件被渲染的时候传进来的 props
。
但这并不意味着由 props
决定的显示形态不能被修改。组件的使用者可以主动地通过重新渲染的方式把新的 props
传入组件当中,这样这个组件中由 props
决定的显示形态也会得到相应的改变。
把需要传递到子组件的参数定义在父组件的state
中, 稍微改写:
class Index extends Component {
constructor () {
super();
this.state = {
bool: false,
obj: {
name: 'tom',
gender: 'male',
}
}
}
handleClick() {
this.setState({
obj: {name: 'tom',gender: 'female'},
})
}
render() {
return (
<div>
<Header />
<Main {...this.state.obj} />
<button onClick={this.handleClick.bind(this)}>点击按钮</button>
<Footer />
</div>
)
}
}
通过点击事件handleClick
修改state里面的变量从而把新的 props
传入组件当中,这样这个组件中由 props
决定的显示形态也会得到相应的改变。
在上面的代码中我们通过点击父组件的按钮来setState
state
中的变量对象, 那么在子组件中如何进行修改呢。
首先定义一个函数来执行setState
作为参数传递到子组件中(需要绑定this)
class Index extends Component {
constructor() {
super()
this.state = {
bool: false,
obj: {
name: 'tom',
gender: 'male',
}
}
}
handleClick() {
this.setState({
obj: {
name: 'Dan',
gender: 'female'
}
})
}
render() {
return (
<div>
<Header />
<Main {...this.state.obj} onClick={this.handleClick.bind(this)} />
<Footer />
</div>
)
}
}
学习:
React.js 小书
React文档
从零开始学 ReactJS