一、React数据流
??????在 React 中,数据是自顶向下单向 流动的,即从父组件到子组件。 ??????state 与 props 是 React 组件中最重要的概念。如果顶层组件初始化 props,那么React 会向下遍历整棵组件树,重新尝试渲染所有相关的子组件。而 state 只关心每个组件自己内部的状态,这些状态只能在组件内改变。把组件看成一个函数,那么它接受了 props 作为参数,内部由 state 作为函数的内部参数,返回一个 Virtual DOM 的实现。
二、组件的三大特性
1、属性(props)
??????props是 properties 的缩写,props 是 React 用来让组件之间互相联系的一种机制,通俗地说就像方法的参数一样。 ??????React 的单向数据流,主要的流动管道就是 props。props 本身是不可变 的。当我们试图改变 props 的原始值时,React 会报出类型错误的警告,组件的 props 一定来自于默认属性 或通过父组件传递 而来。如果说要渲染一个对 props 加工后的值,最简单的方法就是使用局部变量或直接在 JSX 中计算结果。 ??????在使用函数组件时无论是否传递参数,props属性都是默认存在的。 举例1:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Props属性</title>
<script src="../js/react.development.js"></script>
<script src="../js/react-dom.development.js"></script>
<script src="../js/babel.min.js"></script>
</head>
<body>
<div id="root"></div>
<script type="text/babel">
let root = document.getElementById('root');
function PropsComponent(props){
if(props){
return (
<div>
<p>Props数据是:</p>
<p>姓名:{ props.name }</p>
<p>年龄:{ props.age }</p>
</div>
)
}else{
return <p>Props数据为空!</p>
}
}
const span = (
<span>
<PropsComponent name='乔峰' age='28'/>
<hr />
<PropsComponent />
</span>
)
ReactDOM.render(span,root)
</script>
</body>
</html>
举例2:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Props属性设置表单的默认值</title>
<script src="../js/react.development.js"></script>
<script src="../js/react-dom.development.js"></script>
<script src="../js/babel.min.js"></script>
</head>
<body>
<div id="root"></div>
<script type="text/babel">
let root = document.getElementById('root')
function UserName(props){
const userName = (
<p>用户名:<input type="text" value={ props.uname }/></p>
)
return userName;
}
function UserPwd(props){
const userPwd = (
<p>密 码:<input type="password" /></p>
)
return userPwd;
}
function FormLogin(){
return (
<div>
<h3>用户登录</h3>
<UserName uname="小红"/>
<UserPwd />
</div>
)
}
ReactDOM.render(<FormLogin/>,root)
</script>
</body>
</html>
(1)React Props默认值与标签属性限制
①可以通过Props对虚拟DOM中的标签属性进行类型、必要性的限制 A、导入prop-types.js库文件:对props属性进行类型、必要性的检查 B、通过PropTypes 对标签属性进行限制 C、在函数组件 中进行类型检查
函数名.propTypes = {
属性名:PropTypes.类型名.必要性限制
}
D、在类 中进行类型检查
static propTypes = {
属性名:PropTypes.类型名.必要性限制
}
类名.propTypes = {
属性名:PropTypes.类型名.必要性限制
}
②在函数组件中使用props属性的defaultProps 来设置组件的默认值
函数名. defaultProps = {
属性名: 默认值
}
举例如下:
...
<body>
<div id="root"></div>
<script type="text/babel">
let root = document.getElementById('root');
function Person(props){
const { name,age,sex } = props;
return (
<ul>
<li>姓名:{ name }</li>
<li>年龄:{ age }</li>
<li>性别:{ sex }</li>
</ul>
)
}
Person.defaultProps = {
name: '张飞',
age: '26',
sex: '男'
}
Person.propTypes = {
name: PropTypes.string.isRequired,
age: PropTypes.number,
sex: PropTypes.string
}
ReactDOM.render(<Person name="AAAAA"/>,root)
</script>
</body>
...
③在类组件中可以使用props属性的defaultProps 来设置组件的默认值,标签可以设置defaultValue属性,该属性的值可以是Props属性的值。
类名.defaultProps = {
属性名: 默认值
}
举例如下:
<body>
<div id="root"></div>
<script type="text/babel">
let root = document.getElementById('root');
class PropsTest extends React.Component{
render(){
const { name,age,address } = this.props
return (
<div>
<label>用户名:
<input type="text" defaultValue={ name }/>
</label>
<br />
<label>年龄:
<input type="text" defaultValue={ age }/>
</label>
<br />
<label>地址:
<input type="text" defaultValue={ address }/>
</label>
</div>
)
}
}
PropsTest.defaultProps = {
name : '刘备',
age: 28,
address: '成都'
}
ReactDOM.render(<PropsTest />,root)
</script>
</body>
//使用static来定义默认值
static.defaultProps = {
属性名: 默认值
}
举例如下:
...
<body>
<div id="root"></div>
<script type="text/babel">
let root = document.getElementById('root');
class PropsTest extends React.Component{
static defaultProps = {
name : '关羽',
age: 28,
address: '成都'
}
static propTypes = {
name:PropTypes.string.isRequired,
age:PropTypes.number,
address:PropTypes.string
}
render(){
const { name,age,address } = this.props
return (
<div>
<label>用户名:
<input type="text" defaultValue={ name }/>
</label>
<br />
<label>年龄:
<input type="text" defaultValue={ age }/>
</label>
<br />
<label>地址:
<input type="text" defaultValue={ address }/>
</label>
</div>
)
}
}
ReactDOM.render(<PropsTest />,root)
</script>
</body>
...
(2)应用-React组件切分与提取
??????对React组件进行切分并提取逻辑清晰、可高度复用的小组件,有利于代码的修改和维护
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>组件的切分和提取</title>
<script src="../js/react.development.js"></script>
<script src="../js/react-dom.development.js"></script>
<script src="../js/prop-types.js"></script>
<script src="../js/babel.min.js"></script>
</head>
<style>
body{
text-align: center;
}
div#root{
position: relative;
width: 500px;
height: auto;
border: 1px solid gray;
margin: 8px auto;
padding: 8px;
text-align: center;
}
div.cssUserDetail{
width: 480px;
height: 320px;
padding: 4px;
border: 1px solid gray;
}
span.cssAvator{
float: left;
width: 32%;
height: auto;
}
span.cssUserInfo{
float: right;
width: 50%;
height: auto;
}
span.cssDate{
float: right;
width: 80%;
height: auto;
}
p.p-center{
text-align: center;
}
p.p-left{
text-align: left;
}
p.p-right{
text-align: right;
}
</style>
<body>
<div id="root"></div>
<script type="text/babel">
let root = document.getElementById('root');
function formatDate(date){
return date.toLocaleDateString();
}
const avator = {
url: '../images/me.png',
alt: 'load...',
nikeName:'曹阿瞒'
}
const userInfo = {
id: '007',
name: '邦德',
gender: '男',
age: 29,
email: 'king@qq.com'
}
const nowDate = {
date : formatDate(new Date())
}
function Avator(props){
return (
<span className='cssAvator'>
<img className = 'cssAvatorImg'
src={ props.avator.url }
alt={ props.avator.alt }
/>
<p className='p-center'>绰号:{ props.avator.nikeName }</p>
</span>
)
}
function UserInfo(props){
return (
<span className='cssUserInfo'>
<p className='p-left'>Id: { props.userInfo.id }</p>
<p className='p-left'>Name: { props.userInfo.name }</p>
<p className='p-left'>Gender: { props.userInfo.gender }</p>
<p className='p-left'>Age: { props.userInfo.age }</p>
<p className='p-left'>Email: { props.userInfo.email }</p>
</span>
)
}
function UserDetail(props){
return (
<div className='cssUserDetail'>
<Avator avator = { props.avator }/>
<UserInfo userInfo = { props.userInfo}/>
<span className = 'cssDate'>
<p className='p-right'>Date:{ props.currDate.date }</p>
</span>
</div>
)
}
const ComDemo = <UserDetail
avator = { avator }
userInfo = { userInfo }
currDate = { nowDate }
/>
ReactDOM.render(ComDemo,root)
</script>
</body>
</html>
2、状态(state)
??????在 React 没有结合 Flux 或 Redux 框架前,它自身也同样可以管理组件的内部状态。在 React 中,把这类状态统一称为state。 ??????React将组件看成是一个状态机 (State Machines) , 通过其内部定义的状态(State) 与生命周期(Lifecycle) 实现与用户的交互, 维持组件不同的状态。 ??????在React框架中定义了一个状态(State) 概念, 并通过状态(State) 来实现React组件的状态机特性。所谓React组件的“状态机”特性, 就是指组件通过与用户的交互, 实现不同的状态,然后通过渲染UI保证用户界面和数据一致性。React框架之所以定义这个状态(State) 概念, 其目的就是仅仅通过更新React组件的状态(State) , 就可以实现重新渲染用户界面的操作(这样就不需要操作DOM了) 。这点也正React设计理念相较于其他前端框架的先进之处。React State的使用方法相比于Props较为复杂一些, 但也是有基本模式的。 State是完全受控于组件的,所定义的属性值可以根据需求而改变,并在虚拟DOM上同步更新。
(1)state的定义
this.state = {
属性名:值
}
(2)更新状态
??????不能直接给状态机中的属性赋值,必须通过setState方法来更新状态机中的属性值。 ??????当组件内部使用库内置的 setState 方法时,最大的表现行为就是该组件会尝试重新渲染。因为我们改变了内部状态,组件需要更新了。setState 是一个异步方法,一个生命周期内所有的 setState 方法会合并操作。
this.setState({
属性名:新的属性值
})
3、生命周期
??????生命周期(life cycle)的概念广泛运用于各行各业。从广义上来说,生命周期泛指自然界和人类社会中各种客观事物的阶段性变化及其规律。自然界的生命周期,可分为出生、成长、成熟、衰退直到死亡。而不同体系下的生命周期又都可以从上述规律中演化出来,运用到软件开发的生命周期上,这二者看似相似,事实上又有所不同。生命体的周期是单一方向不可逆的过程,而软件开发的生命周期会根据方法的不同,在完成前重新开始。 ??????每个组件都包含 “生命周期方法”,可以重写这些方法,以便于在运行过程中特定的阶段执行这些方法。 ??????React 组件的生命周期根据广义定义描述,可以分为挂载 、渲染 和卸载 这几个阶段。当渲染后的组件需要更新时,我们会重新去渲染组件,直至卸载。 ??????因此,我们可以把 React 生命周期分成两类:
当组件在挂载或卸载时; 当组件接收新的数据时,即组件更新时。
(1)挂载(mount)
??????组件挂载是最基本的过程,这个过程主要做组件状态的初始化,当组件实例被创建并插入 DOM 中时,其生命周期调用顺序如下:
import React, { Component, PropTypes } from 'react';
class App extends Component {
static propTypes = {
};
static defaultProps = {
};
constructor(props) {
super(props);
this.state = {
};
}
componentWillMount() {
}
render() {
return <div>This is a demo.</div>;
}
componentDidMount() {
}
}
propTypes:props 类型检查 defaultProps:props默认类型
??????这两个属性被声明成静态属性 ,意味着从类外面也可以访问它们,比如可以这么访问:App.propTypes和 App.defaultProps。
constructor(props) :构造方法
??????如果不初始化 state 或不进行方法绑定,则不需要为 React 组件实现构造函数。在 React 组件挂载之前,会调用它的构造函数。在为 React.Component 子类实现构造函数时,应在其他语句之前调用 super(props)。否则,this.props 在构造函数中可能会出现未定义的 bug。 ??????通常,在 React 中,构造函数仅用于以下两种情况: 通过给 this.state 赋值对象来初始化内部 state 、为事件处理函数绑定实例 ??????在 constructor() 函数中不要调用 setState() 方法。如果你的组件需要使用内部 state,请直接在构造函数中为 this.state 赋值初始化state:
constructor(props){
super(props);
this.state = {
};
this.handleClick = this.handleClick.bind(this);
}
componentWillMount:在 render 方法之前执行 componentDidMount:在 render 方法之后执行
??????它们分别代表了渲染前后的时刻,这个初始化过程没什么特别的,包括读取初始 state 和 props 以及两个组件生命周期方法componentWillMount 和 componentDidMount,这些都只会在组件初始化时运行一次。
render()
??????是 class 组件中唯一必须实现的方法 ,当 render 被调用时,它会检查 this.props 和 this.state 的变化并返回以下类型之一: ①React 元素 ,通常通过 JSX 创建。例如,< div / > 会被 React 渲染为 DOM 节点,< MyComponent /> 会被 React 渲染为自定义组件,无论是 < div /> 还是 < MyComponent /> 均为 React 元素。 ②数组或 fragments ,使得 render 方法可以返回多个元素。 ③Portals ,可以渲染子节点到不同的 DOM 子树中。 ④字符串或数值类型 。它们在 DOM 中会被渲染为文本节点。 ⑤布尔类型或 null 。什么都不渲染。(主要用于支持返回 test && < Child /> 的模式,其中 test 为布尔类型。) ??????render() 函数应该为纯函数 ,这意味着在不修改组件 state 的情况下,每次调用时都返回相同的结果,并且它不会直接与浏览器交互。如需与浏览器进行交互,要在 componentDidMount() 或其他生命周期方法中执行操作。保持 render() 为纯函数,可以使组件更容易使用、维护。
(2)数据更新过程
??????更新过程指的是父组件向下传递 props 或组件自身执行 setState 方法时发生的一系列更新动作。这里我们屏蔽了初始化的生命周期方法,以便观察更新过程的生命周期:
import React, { Component, PropTypes } from 'react';
class App extends Component {
componentWillReceiveProps(nextProps) {
}
shouldComponentUpdate(nextProps, nextState) {
}
componentWillUpdate(nextProps, nextState) {
}
componentDidUpdate(prevProps, prevState) {
}
render() {
return <div>This is a demo.</div>;
}
}
??????如果组件自身的 state 更新了,那么会依次执行shouldComponentUpdate、componentWillUpdate 、render 和 componentDidUpdate。 ??????shouldComponentUpdate 是一个特别的方法,它接收需要更新的 props 和 state,让开发者增加必要的条件判断,让其在需要时更新,不需要时不更新。因此,当方法返回 false 的时候,组件不再向下执行生命周期方法。shouldComponentUpdate 的本质是用来进行正确的组件渲染。 ??????componentWillUpdate 和 componentDidUpdate 这两个生命周期方法很容易理解,对应的初始化方法也很容易知道,它们代表在更新过程中渲染前后的时刻。此时,我们可以想到 componentWillUpdate 方法提供需要更新的 props 和 state,而 componentDidUpdate 提供更新前的 props 和state。
??????如果组件是由父组件更新 props 而更新的,那么在 shouldComponentUpdate 之前会先执行componentWillReceiveProps 方法。此方法可以作为 React 在 props 传入后,渲染之前setState 的机会。在此方法中调用 setState 是不会二次渲染的。
(3)卸载
??????组件卸载非常简单,只有 componentWillUnmount 这一个卸载前状态,在componentWillUnmount 方法中,我们常常会执行一些清理方法,如事件回收或是清除定时器。
|