语法:
- 引入Component
- 新建一个类,继承Component
- 实现render函数
- 在render函数内返回一个jsx,并导出
- 入口文件引入该组件
- 引入时的名字作为组件标签
import React,{Component} from 'react';
export default class hello extends Component{
render(){
return (
<div>
<h1>组件语法学习</h1>
</div>
)
}
}
----------------------------------------------------------------------
import Hello from './component/component';
ReactDOM.render(
<div>
<Hello></Hello>
<p
style={{
border : '1px solid #000'
}}
>hell,react
</p>
</div>,
document.getElementById('root')
)
prop
ReactDOM.render(
<div>
<Hello name='小明' age='18' hubby='学习react'>
<p>我是children</p>
</Hello>
<p
style={{
border : '1px solid #000'
}}
>hell,react
</p>
</div>,
document.getElementById('root')
)
import React,{Component} from 'react';
export default class hello extends Component{
render(){
let {name,age,hubby} = this.props
return (
<div>
<h1>组件语法学习</h1>
<p>姓名:{name}</p>
<p>年龄:{age}</p>
<p>爱好:{hubby}</p>
{this.props.children}
</div>
)
}
}
props.children
查看如下代码:
ReactDOM.render(
<div>
<Ment num={9} name="Moli">
<p>网速太卡了!!</p>
</Ment>
</div>
, document.getElementById('root')
);
我们在使用 Ment 组件的时候, 我们在标签之间插入了一个 p 标签, 但是你会发现 p 标签的内容并没有渲染到页面上. 💢💢
事实上, 以下两种写法是等价的: 🌟🌟
<Ment num={9} name="Moli">
<p>网速太卡了!!</p>
</Ment>
// === 这两种写法等价
<Ment
num={9}
name="Moli"
children={<p>网速太卡了!!</p>}
/>
发现了么, 写在组件标签之间的内容本质上只是给组件传递了一个 children 属性.
所以, 要想 p 标签显示出来, 就得用上它.
比如: 在组件实现上, 你可以这样:
function Ment(props) {
return (
<div>
<h2>Hello {props.name}</h2>
{props.num} people here!
{props.children}
</div>
)
}
state: 内部状态
确定一个共识: 类组件才有内部状态!
一个组件在某些时候需要作出一些改变. 它不可能总是一成不变的.
比如一个计数器, 在某一次点击过后, 现实的数字就会改变; 再比如一个时钟程序, 现实的数字随着时间变动.
现在我们尝试做一个计数器看看.
这是我们的基础代码:
import React from 'react';
import ReactDOM from 'react-dom';
class Counter extends React.Component{
render(){
return (
<div>
<p>目前计数: 0</p>
<button>计数 +1</button>
</div>
)
}
}
ReactDOM.render(
<Counter/>
, document.getElementById('root')
);
Counter
组件现在渲染之后是这个样子的:
给组件添加状态
现在我们改造一下 Counter 组件:
class Counter extends React.Component{
constructor(props){
super(props);
this.state= {
count: 0
};
}
render(){
let { count } = this.state;
return (
<div>
<p>目前计数: {count}</p>
<button>计数 +1</button>
</div>
)
}
}
定义状态
我们首先增加了构造函数 constructor()
:
constructor(props){
super(props);
this.state = {
count: 0
};
}
构造函数接收的第一个参数就是我们之前学的 props
;
我们先使用 super(props);
把组件的 props
传给父类的构造函数, 否则在构造函数里, 即便传递了 props
, this.props
的值也会是 undefined
.
最关键的:
我们组件的实例上赋予了一个 state
变量. 并让它的值是一个对象.
state
的值, 要么是一个对象: {}
, 要么是 null
.
这样, 组件便有了一个内部状态.
使用状态
在类的任何地方, 我们都可以通过组件实例拿到这个状态并使用.
比如在 render()
方法里面:
render(){
let { count } = this.state;
return (
<div>
<p>目前计数: {count}</p>
<button>计数 +1</button>
</div>
)
}
我们访问了 this.state.count
的值, 并渲染了它.
改变状态
现在我们想做一件事情, 点击按钮, count
的值就 +1.
我们需要先给按钮添加一个点击事件:
render(){
let { count } = this.state;
return (
<div>
<p>目前计数: {count}</p>
<button
onClick={()=>{
this.setState({
count: count +1
})
}}
>计数 +1</button>
</div>
)
}
现在你点击按钮, 就会发现数字会出现变化.
这里有一些关键点:
添加事件我们会在后面详细说, 现在简单说一下, 给元素一个 onClick
的属性, 就添加了点击事件, 事件接收一个回调函数.
要想改变 count
的值, 你不能直接修改 this.state
, 而应该使用组件实例的 this.setState()
接口.
另外, 如果你的 state
属性很多, 比如:
state= {
count: 0,
c1: 0,
c2: 0
};
如果你只想改变 c1
的值, 那么只需:
this.setState({
c1: 2
})
就可以了.
何为内部状态
如果你渲染多个 Counter
的实例:
ReactDOM.render(
<div>
<Counter/>
<Counter/>
<Counter/>
</div>
, document.getElementById('root')
);
点击不同的按钮, 查看界面:
image.png你会发现, 组件实例之间的状态互不影响. 这也是为什么我们把组件的 state
称为内部状态.
State 的重要特性
本节的内容说的比较抽象. 你需要使用代码调试来理解.
State 会合并更新
比如你的状态是这样的:
this.state= {
c1: 0,
c2: 0
};
你可以这样去只更新其中的一部分:
this.setState({
c1: 5,
});
this.setState({
c2: 8,
});
这里就自然地引出了 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
}
上面我们进行了三次 setState,但是实际上组件只会重新渲染一次,而不是三次;这是因为在 React.js 内部会把 JavaScript 事件循环中的消息队列的同一个消息中的 setState 都进行合并以后再重新渲染组件。
深层的原理并不需要过多纠结,你只需要记住的是:在使用 React.js 的时候,并不需要担心多次进行 setState 会带来性能问题。
state 通常是异步更新
如果现在 c1
的值是 0, 然后你更新:
// 现在 c1 = 0
this.setState({
c1: 2
});
console.log(this.state.c1) // 打印 0
这个时候打印的结果是 0
. 原因是调用 this.setState()
后, c1
的值不会立即发生更新.
在异步执行的函数里面, state 会同步更新
正常情况下 :更新是异步的,setState会合并更新。
异步执行的函数里面:setState会同步更新。
setTimeout、异步请求的回调函数(ajax),Promise
要注意性能问题,异步是同步更新的,所以要注意一下state的效率。
比如一个页面有多个数据需要请求,加载一个页面的时候比如有5个图表,请求数据之后渲染图表,如果是不同的接口,就要写多个请求函数,一旦这样,数据就是同步改变。
查看如下代码:
// 现在 c1 = 0
setTimeout(()=>{
this.setState({
c1: 2
});
console.log(this.state.c1) // 打印 2
})
这个时候结果打印 2
, 如果 this.setState()
在一个异步函数里面调用, 那么 state
会立即更新.
以下的函数同样是这种情况:
- Promise 的回调函数
- ajax 响应后的回调函数
只要是异步执行的函数, 就适用这种情况.
this.setState() API
setState() 第一个参数可以传入一个对象, 这种使用方式我们已经知道.
第一个参数还可以传入一个函数:
this.setState((preState, props)=>{
return {
c1: preState.c1 + 1
}
});
preState
是之前的 state
props
是组件的 props
函数返回的就是要更新的组件状态.
再看看如下代码:
// c1 此时是 0
this.setState((preState, props)=>{
console.log(preState.c1); // 打印 0
return {
c1: 2
}
});
console.log(this.state.c1); // 打印 0
this.setState((preState, props)=>{
console.log(preState.c1); // 打印 2
return {
c1: 5
}
});
console.log(this.state.c1); // 打印 0
仔细查看这几次打印, state 异步更新的情况没有变.
但 preState
的值是前一次 setState()
调用之后, 得到的 State
;
第二个参数
setState() 第二个参数是一个可选的毁掉函数, 当组件更新完成之后, 会调用.
state 的更新只是浅层合并
如果这是现在 state
的情况:
this.state = {
c1: 0,
c2: {a:1, b: 2}
}
现在你这样更新:
this.setState({
c2: {a: 40}
})
这个时候, 最终的 state
会变成这样:
this.state = {
c1: 0,
c2: {a:40}
}
c2
的 b
属性不见了. 因为整个对象都会换掉了.
setState()
之后进行 1 个层级的浅层合并.
// setState如果直接传对象,会覆盖掉之前的,如果传callback,就可以获取之前的state
export default class Number extends Component{
constructor(props){
super(props)
this.state = {
magicNumber : Math.random().toString().slice(2,6),
a:{
m1:1,
m2:2
}
}
}
changeNumber=()=>{ //更新a的数据
this.setState((prevState,props)=>{
return {
a:{
...prevState.a,
m1:2
}
}
})
this.setState((prevState,props)=>{
return {
a:{
...prevState.a,
m2:3
}
}
})
}
}
总结
为了使得组件的可定制性更强,在使用组件的时候,可以在标签上加属性来传入配置参数。
组件可以在内部通过 this.props 获取到配置参数,组件可以根据 props 的不同来确定自己的显示形态,达到可配置的效果。
可以通过给组件添加类属性 defaultProps 来配置默认参数。
props 一旦传入,你就不可以在组件内部对它进行修改。但是你可以通过父组件主动重新渲染的方式来传入新的 props,从而达到更新的效果。
我们来一个关于 state 和 props 的总结。
state 的主要作用是用于组件保存、控制、修改自己的可变状态。state 在组件内部初始化,可以被组件自身修改,而外部不能访问也不能修改。你可以认为 state 是一个局部的、只能被组件自身控制的数据源。state 中状态可以通过 this.setState 方法进行更新,setState 会导致组件的重新渲染。
props 的主要作用是让使用该组件的父组件可以传入参数来配置该组件。它是外部传进来的配置参数,组件内部无法控制也无法修改。除非外部组件主动传入新的 props,否则组件的 props 永远保持不变。
区别:
state 是让组件控制自己的状态,props 是让外部对组件自己进行配置。
- state 和 props 有着千丝万缕的关系。它们都可以决定组件的行为和显示形态。一个组件的 state 中的数据可以通过 props 传给子组件,一个组件可以使用外部传入的 props 来初始化自己的 state。但是它们的职责其实非常明晰分明:state 是让组件控制自己的状态,props 是让外部对组件自己进行配置。
<ul className="listWrap">
{
this.state.datas.map((elt,i)=>{
return (
<LiDom
id={elt.id}
content={elt.content}
deleteValue={this.deleteValue}
index={i}
></LiDom>
)
})
}
</ul>
class LiDom extends Component{
render(){
let {id,content,deleteValue,index} = this.props
return (
<li key={id}>
<span>{index+1}.{content}</span>
<button onClick={()=>deleteValue(id)}>删除</button>
</li>
)
}
// 更新前执行的,所以在没有更新之前
shouldComponentUpdate(nP,nS){
return !this.props.content === nP.content
}
}
如果你觉得还是搞不清 state 和 props 的使用场景,那么请记住一个简单的规则:尽量少地用 state,尽量多地用 props。
- 没有 state 的组件叫无状态组件(stateless component),设置了 state 的叫做有状态组件(stateful component)。因为状态会带来管理的复杂性,我们尽量多地写无状态组件,尽量少地写有状态的组件。这样会降低代码维护的难度,也会在一定程度上增强组件的可复用性。前端应用状态管理是一个复杂的问题,我们后续会继续讨论。
React.js 非常鼓励无状态组件,在 0.14 版本引入了函数式组件——一种定义不能使用 state 组件,例如一个原来这样写的组件:
class HelloWorld extends Component {
constructor() {
super()
}
sayHi () {
alert('Hello World')
}
render () {
return (
<div onClick={this.sayHi.bind(this)}>Hello World</div>
)
}
}
用函数式组件的编写方式就是:
const HelloWorld = (props) => {
const sayHi = (event) => alert('Hello World')
return (
<div onClick={sayHi}>Hello World</div>
)
}
以前一个组件是通过继承 Component 来构建,一个子类就是一个组件。而用函数式的组件编写方式是一个函数就是一个组件,你可以和以前一样通过 <HellWorld /> 使用该组件。不同的是,函数式组件只能接受 props 而无法像跟类组件一样可以在 constructor 里面初始化 state。你可以理解函数式组件就是一种只能接受 props 和提供 render 方法的类组件。
网友评论