react 安装
通过 npm 使用 React
国内使用 npm 速度很慢,你可以使用淘宝定制的 cnpm (gzip 压缩支持) 命令行工具代替默认的 npm:
npm install -g cnpm --registry=https://registry.npm.taobao.org
npm config set registry https://registry.npm.taobao.org
这样就可以使用 cnpm 命令来安装模块了:
cnpm install [name]
使用 create-react-app(脚手架库) 快速构建 React 开发环境
create-react-app 是来自于 Facebook,通过该命令我们无需配置就能快速构建 React 开发环境。
create-react-app 自动创建的项目是基于 React + Webpack + ES6 + eslint 。
执行以下命令创建项目:
第一步,全局安装: cnpm install -g create-react-app
第二步,切换到想创建项目的目录: create-react-app my-app
第三步,进入项目文件夹: cd my-app
第四步,启动项目: npm start
npm start 开启开发服务,运行我们开发时环境
npm run build 项目开发完打包生成静态文件交给后端进项上线
npm test 测试(一般很少用)
npm run eject 默认webpack配置是隐藏的,执行此命令后会显示出来不会再隐藏了
VS Code 编辑器安装插件:
创建一个 js/jsx 文件:
rcc 创建一个 类 组件
rfc 创建一个 函数 组件
查看react版本及升级
当前的react版本在你的项目中的package.json文件查看
查看react最新版本
npm info react
安装react指定版
// @后面输入版本号
npm install --save react@17.0.1
create-react-app升级
第一步需要先查看create-react-app最新版本
npm info create-react-app
接着查看你当前的create-react-app版本,如果你的版本是小于最新版本的话,那就需要继续往下看。
create-react-app -V
or
create-react-app --version
第二步:如果你的版本是小于上面这个最新版本的话。那么需要先卸载掉原先的旧版本: npm uninstall -g create-react-app
1、在卸载过程中如果遇到一个错误,这个错误返回并提示create-react-app,create-react-app.cmd这两个文件的路径时,那么就要在电脑上找到对应的文件并删除,我的提示的路径是在这
删除了之后再次运行查看是否删除成功:create-react-app -V 或 create-react-app --version
2、接着继续安装npm install -g create-react-app,如果在安装过程中报如下错误时,那么就要找到create-react-app的文件夹位置并删除
这两步操作之后,继续全局安装npm install -g create-react-app,并查看版本号
一、什么是React
React 是一个用于构建用户界面的 JAVASCRIPT 库。
React 主要用于构建UI,很多人认为 React 是 MVC 中的 V(视图)。
React 起源于 Facebook 的内部项目,用来架设 Instagram 的网站,并于 2013 年 5 月开源。
React 拥有较高的性能,代码逻辑非常简单,越来越多的人已开始关注和使用它。
React 特点:
-
1.声明式设计 −React采用声明范式,可以轻松描述应用。
-
2.高效 −React通过对DOM的模拟,最大限度地减少与DOM的交互。
-
3.灵活 −React可以与已知的库或框架很好地配合。
-
4.JSX − JSX 是 JavaScript 语法的扩展。React 开发不一定使用 JSX ,但我们建议使用它。
-
5.组件 − 通过 React 构建组件,使得代码更加容易得到复用,能够很好的应用在大项目的开发中。
-
6.单向响应的数据流 − React 实现了单向响应的数据流,从而减少了重复代码,这也是它为什么比传统数据绑定更简单。
React 面向组件编程-----初始化显示
问题1:数据应该保存在哪个组件内?
回答:若是某个组件需要,则写在该组件内;若是多个组件需要,则写在这些组件共同的父组件内。
问题2:如何在子组件中修改父组件的数据状态?(第二篇文章的 state状态提升)
回答:子组件中不能直接改变父组件中的state状态;状态在哪个组件,更新状态的行为就定义在哪个组件
解决方案:父组件中定义行为函数,并将函数传递给子组件,子组件通过调用该函数(传参)来达到修改状态。
一个最简单的React例子:
React 元素渲染
元素是构成 React 应用的最小单位,它用于描述屏幕上输出的内容。
const element = <h1>Hello, world!</h1>;
与浏览器的 DOM 元素不同,React 当中的元素事实上是普通的对象,React DOM 可以确保 浏览器 DOM 的数据内容与 React 元素保持一致。
render():渲染虚拟DOM到真实DOM,render(virtualDOM, containerDOM)
,即render(虚拟DOM, 真实DOM)
// 1.创建虚拟DOM对象
const element = <h1>Hello, world!</h1>; // 不是字符串;JSX语法
// 2.将虚拟DOM渲染到真实DOM容器中
ReactDom.render(
element,
document.getElementById('root')
)
要将React元素渲染到根DOM节点中,我们通过把它们都传递给 ReactDOM.render() 的方法来将其渲染到页面上;
ReactDom.render()
接受两个参数,第一个是要被插入的内容,第二个是插入到DOM或者说index.html
的位置
一个与Html对比的简单组件:
class ShoppingList extends React.Componnet {
render() {
return (
<div className="shopping-list">
<h1>Shoping List for {this.props.name}</h1>
<ul>
<li>Instagram</li>
<li>WhatApp</li>
<li>Oculus</li>
</ul>
</div>
)
}
}
// Example usage: <ShoppingList name="muzidigbig" />
在这里,ShoppingList 是一个 React组件类,或 React组件类型。组件接受参数,称为属性 props
, 并通过 render
方法返回一个现实的视图层次结构。
render
方法返回您要渲染的内容描述,然后React接受该描述并将其渲染到屏幕上,特别是,render
返回一个React 元素,这是一个渲染内容的轻量级的描述。大多数
React 开发人员使用 JSX
语法,也是上述案例写到的语法。
JSX
语法的转换规则为: <div />
语法在构建是被转换为 React.createElement('div')
。因此,上面的例子等价于:
createElement():
createElement(标签名, { 属性: 属性名 }, 内容)
return React.createElement('div', {className: 'shopping-list'},
React.createElement('h1', /* h1 children ... */),
React.createElement('ul', /* ul children ... */)
);
既然 JSX
在 React 开发者中这么流行,那 JSX
又是什么呢?
二、JSX 语法
JSX
:看起来像HTML的 Javascript
一种拓展语法,能够让你的 Javascript
中和正常描述 HTML一样编写 HTML。
使用 JSX 语法可读性更强,书写更方便
JSX语法中,使用 {} 代替 ""
元素是构成 React 应用的最小单位,JSX 就是用来声明 React 当中的元素。
/*
jsx语法规则:
1.定义虚拟DOM时,不能写引号
2.标签中混入JS表达式时要用 {}
3.样式的类名指定不要用 class,要用 className
4.内联样式,要用 style={{ key:value,color: '#999', fontSize: '30px' }} 的形式去写
5.只有一个根标签
6.标签必须闭合
7.标签首字母
1)若小写字母开头,则将该标签为html中同名元素,若html中无该标签对应的同名元素,则报错
2)若大写字母开头,react就去渲染对应的组件,若组件未定义,则报错。
8.注释: {/* <input type="text" ref={c => this.input1 = c} /> */}
*/
在Google添加扩展程序:react developer tool
直接运行会很慢,所以在真实项目中一般先编译后运行。
我们不需要一定使用 JSX,但它有以下优点:
- JSX 执行更快,因为它在编译为 JavaScript 代码后进行了优化。
- 它是类型安全的,在编译过程中就能发现错误。
- 使用 JSX 编写模板更加简单快速。
你可以用 花括号
将任意 Javascript
表达式嵌入到 JSX
中。例如:表达式 1 + 2
, 变量 user.firstName
, 和函数 formatName(User)
等都可以嵌入使用
function formatName(user) {
return user.firstName + ' ' + user.lastName;
}
const user = {
firstName: 'harper',
lastName: 'Perez'
}
const element = {
<h1> Hello, {formatName(user)}! </h1>
}
ReactDOM.render (
element,
document.getElementById('root')
)
数据带页面标签的不能直接专一,具体转义写法如下:
<div dangerouslySetInnerHTML={{__html: 'cc © 2015'}} />
自定义 HTML 属性
如果在 JSX 中使用的属性不存在于 HTML 的规范中,这个属性会被忽略。如果要使用自定义属性,可以用 data-
前缀。
请注意,为了方便阅读开发者们常将 JSX
分割成多行包裹起来,因为这可以避免分号自动插入的陷阱,如
{ 1
2 } 3
// is transformed to
{ 1
;2 ;} 3;
用 JSX 指定属性值
你可以用花括号嵌入一个 JavaScript 表达式作为属性值
// 用引号形式
const element = <div tableIndex="0"></div>;
// 用表达式,并且表达式用花括号包裹
const element = <img src={user.avatarUrl}></img>;
用 JSX 指定子元素
如果是空标签,可以直接用 />
闭合
const element = <img src={user.avatarUrl} />
如果包含子标签(外面包裹一个<div>)
<div>
<h1>Hello!</h1>
<h2>Good to see you here.</h2>
</div>
比起
HTML
,JSX
更接近于Javascript
,所以React DOM
规范使用驼峰(camelCase)属性命名约定,而不是HTML属性名称,当然,html的部分属性名称也作为保留字,不可使用,例如class
;
因此,class
在 JSX 中 变为className
,tableindex
变为tableIndex
。
用 JSX 防止注入攻击
在JSX 中嵌入用户输入是安全的:
const title = response.potentiallyMaliciousInput;
// 这样是安全的
const element = <h1>{title}</h1>
默认情况下, 在渲染之前, React DOM 会格式化(escapes) JSX中的所有值. 从而保证用户无法注入任何应用之外的代码. 在被渲染之前,所有的数据都被转义成为了字符串处理。 以避免 XSS(跨站脚本) 攻击。
元素渲染到DOM
正常情况下,你的 index.html
文件下会有这么一个div
<div id='root'></div>
这个root
DOM 节点挂在所有React DOM的位置。正常情况下,对于一个React单页面应用构建,只需要一个单独的根DOM节点即可。但如果要把React整合到现有的APP中,则可能会使用到多个DOM节点。
React利用render
方法将React元素渲染到DOM上,一旦元素被渲染到页面了之后,就不能在修改器子元素或任何元素的属性,就像电影里的一帧,在某以特定的时间点的UI效果,那元素的更新呢?没错,就是重新 render
function tick() {
cosnt element = {
<div>
<h1>Hello, world</h1>
<h2>It is {new Date().toLocaleTimeString()}.</h2>
</div>
};
ReactDom.render (
element,
document.getElementById('root')
)
}
setInterval(tick, 1000);
我们可以在 JSX 中使用 JavaScript 表达式。表达式写在花括号 {} 中。
实际上,大多数 React 应用只会调用一次ReactDom.render(),而实现组件更新的办法就是将代码封装在有状态的组件中。
React 只更新必须更新的部分
这正是 React 的强大之处。React DOM 会将元素及其子元素与之前版本逐一对比,并只对有必要更新的 DOM 进行更新, 以达到 DOM 所需的状态。
开发过程中,更应该每个时间点UI的表现, 而不是关注随着时间不断更新UI的状态, 可以减少很多奇怪的 bug
三、组件和属性
组件 components
和属性 props
,其中,属性是单词 property
的代码简写。
定义组件的两种办法
定义组件有两种方式
- 函数式组件定义
- 类组件定义(常用)
最简单的定义组件的方法就是写一个Javascript
函数
function Welcome(props) {
return <h1>Hello, props.name</h1>
}
这就是一个有效的组件,它接首了一个 props
参数,并返回了一个React元素,这是一个函数式组件,表面上看,他就是一个 Javascript
函数。
类组件的定义则依赖ES6 的 class
来定义,下面这种定义方法和上方是等效的:
important React from 'react';
class Welcome extends React.Component {
render() {
return <h1>Hello, {this.props.name}</h1>;
}
}
渲染一个组件
// DOM标签作为组件
const element = <div />;
// React 元素作为组件
const element = <Welcome name="muzidigbig" />;
当React 遇到一个代表用户定义组件的元素时,它将 JSX
属性以一个单独对象即props对象
的形式传递给相应的组件,例如
function Welcome(props) {
return <h1>Hello, {props.mname] </h1>;
}
const element = <Wlecome name="muzidigbig" />;
ReactDOM.render(
element,
document.getElementById('root')
)
【理解】
- 调用
ReactDOM.render()
方法并向其传入了<Welcome name="muzidigbig" />组件
- Raect 调用
Welcome
组件,发现组件是函数定义的,随后调用该函数,并向其传入了{name: 'muzidigbig'}
作为props对象
Welcome
组件返回<h1>Hello, muzidigbig</h1>(虚拟DOM)
- React DOM 迅速更新为真实 DOM,使其页面显示为
<h1>Hello, muzidigbig</h1>
注意: 组件名称必须以大写字母开头。
原生 HTML 元素名以小写字母开头,而自定义的 React 类名以大写字母开头,比如 HelloMessage 不能写成 helloMessage。除此之外还需要注意组件类只能包含一个顶层标签,否则也会报错。
React 会将以小写字母开头的组件视为原生 DOM 标签。例如,
<div />
代表 HTML 的 div 标签,而<Welcome />
则代表一个组件,并且需在作用域内使用Welcome
。
构成组件
既然组件是单独的一个React元素,那他能单独工作,因此我们能在一个React 元素中多次引用到相同的组件, 举个例子:
function Welcome(props) {
return <h1>Hello, {props.name}</h1>
}
function App() {
return (
<Welcome name="Sara" />
<Welcome name="Lucy" />
<Welcome name="Edite" />
)
}
ReactDOM.render(
<App />,
document.getElementById('root')
)
通常情况下, React apps 都有一个单独的顶层的 App
组件。如果是在已有的应用中整合React,也需要由下至上的从小的组件开始逐步整合到视图顶层的组件中。
组件必须返回一个单独的根元素,这就是为什么我们要添加一个
<div>
来包裹所有的<Welcome />
元素的原因
提取组件
对于一个React 元素,如果其中含有可复用或可能会重复使用的内容,不要害怕把它单拿出来多个更小的组件。
提取组件可能看起来是一个繁琐的工作,但是在大型的 App
中可以回报给我们的是大量的可复用组件。一个好的经验准则是如果你 UI 的一部分需要用多次 (Button
,Panel
,Avatar
),或者本身足够复杂(App
,FeedStory
,Comment
),最好的做法是使其成为可复用组件。
Props 是只读的
无论你用函数或类的方法来声明组件,
虽然 React 很灵活,但是它有一条严格的规则:**所有 React 组件都必须是纯函数,并禁止修改其自身 props **。所谓的纯函数就是:传入函数参数不会在函数执行过程中发生改变,比如自增操作 a++
。
如果props
是只读的,那传递给子元素(子组件)的参数岂不是不能修改了?那子元素如何与父元素做交互呢?React还给我们提供了状态属性 state
供我们在子组件内部修改值
四、状态和生命周期
状态state
, 生命周期 liftcircle
.
之前说过,一旦元素被渲染了之后就不可改变了,但我们可以通过重新渲染的方法使页面得以刷新,同样我们提到过最常用的方法是编写一个可复用的具有状态的组件,这里的状态,就是我们将要说的 state
state
我们对上述提过的计时器tick
中的计时功能封装成一个函数式组件如下:
function Clock(props) {
return (
<div>
<h1>Hello, world!</h1>
<h2>It is {props.date.toLocaleTimeString()}.</h2>
</div>
)
}
在这个例子中,我们将计时功能代码封装成了一个独立的可复用的组件,并通过属性date的方式将参数传入,但还不能到达我们想要的结果,那就是不能再组件内部修改参数值,组件中显示的数据依旧受控于父组件中date
属性传递过来的值,那如果我们把这个date
属性也添加到Clock
内部呢?来看下
ReactDOM.render(
<Clock />,
document.getElementById('root')
)
这时父组件中只保留了对计时组件Clock
的一个单纯的引用。剩下的事情全部依托以组件Clock
自己去实现。要怎么实现这个需求?这里React提出了另一个数据对象,即state
,它用来保存组件内部的数据,与props
类似,不同的是state
是组件私有的,并且由组件本身完全控制。它能实现数据在组件内部的修改和更新。怎么使用这个state
?继续往下讲之前我们先拓展一个知识
我们知道组件有两种定义方式,即函数式组件和类组件,虽然函数式组件更加简洁更加接近原生 javascript
,但类组件却拥有一些额外的属性,这个类组件专有特性,就是状态和生命周期钩子,到这里也能清楚知道状态的关键作用,然而函数式组件没有这两个特性,因此,在需要使用到状态state
情况下,我们需要将函数式组件转换成类组件
函数式组件转化成类组件
尝试把一个函数式组件转化成类组件,官网给出了以下步骤,以Clock
组件为例
- 创建一个继承自
React.Component
类的ES6 class
同名类 - 添加一个名为
render(){return ...}
的空方法 - 把原函数中的所有内容移至
render()
中 - 在
render()
方法中使用this.props
替代props
- 删除保留的空函数声明
class Clock extents React.Component {
render() {
return (
<div>
<h1>Hello, world</h1>
<h2>It is {this.props.date.toLocaleTimeString()}.</h2>
</div>
)
}
}
到此,Clock
组件已经成功被我们修改成了一个类组件,我们便可以在其中添加本地状态state
和生命周期钩子
// 创建组件
class Clock extends React.Component {
// 用类构造函数constructor初始化 this.state
constructor(props) {
// 使用super()将props传递给基础构造函数,固定格式
super(props);
// 设定初始state
this.state = {date: new Date()};
}
render() {
return (
<div>
<h1>Hello, world</h1>
<h2>It is {this.state.date.toLocaleTimeString()}.</h2>
</div>
)
}
}
state的简写方式
// let isHot = false;
// 1.创建类式组件(首字母大写)
class Weather extends React.Component {
/* constructor(props) {
super(props)
// 初始化状态
// this.state = { isHot: false }
// 解决 changeWeather 中this指向问题
// this.changeWeather = this.changeWeather.bind(this)
} */
/*
类中可以直接写赋值语句,如下代码的含义是:给Weather的实例对象添加一个属性,名为a,值为4
a = 4
*/
// 初始化状态
state = { isHot: false }
// 自定义方法--调用赋值语句的形式+箭头函数
changeWeather = () => {
let { isHot } = this.state
this.setState({ isHot: !isHot })
}
render() {
console.log('render中的this:', this)
let { isHot } = this.state
return <h1 onClick={this.changeWeather}>今天天气{isHot ? '炎热' : '凉爽'}</h1>;
}
}
// 2.渲染组件到页面
ReactDOM.render(<Weather />, document.querySelector("#test"))
/*
ReactDOM.render(<MyComponent />.... 发生了什么?
1.React解析组件标签,找到 MyComponent 组件。
2.发现组件是使用类定义的,随后new出该类的实例,并通过该实例调用原型上的 render 方法。
3.将 render 返回的虚拟DOM转为真实DOM,随后呈现在页面上。
*/
function demo(params) {
console.log('标题被点击了');
}
这样,我们的类组件Clock
就拥有了自己的属性 this.state.date
,也就不需要引用组件向其传递值了,因此,我们可以把组件引用中的date
属性删掉,最终,我们将其渲染到DOM上,只使用组件引用,其他都交给组件Clock
自己去实现
ReactDOM.render(
<Clock />,
document.getElementById('root')
)
到这里就结束了?细心的你会发现,组件Clock
只是实现了当前时间的显示,而我们要改装的功能是一个计时器,计时功能去哪里了?没实现啊?我们需要在组件Clock
中找到一个合适的时机去实现这个功能,为此,React团队引入了 声明周期方法,也叫生命周期钩子
在类组件中添加生命周期方法
在一个具有许多组件的应用程序中,在组件被销毁时释放所占用的资源是非常重要的。就像浏览器的垃圾回收机制,近期内不需要再用的资源,应该及时清除。
当 Clock
第一次渲染到DOM时,我们要设置一个定时器 。 这在 React 中称为 “挂载(mounting)” 。它有一个生命钩子componentDidMount()
当 Clock
产生的 DOM 被销毁时,我们也想清除该计时器。 这在 React 中称为 “卸载(unmounting)” 。它的生命钩子是componentWillUnmount()
我们的计时器是在页面加载之后,页面生成初始化状态,然后由计时器去触发状态的刷新,因此,在挂载完成是去设置计时器是个非常不错的选择
componentDidMount() {
this.timerID = setInterval(
() => this.tick(), 1000
)
}
挂载时我们声明了一个tick()
方法,接下来我们就要实现这个方法,是用来触发UI更新。嗯哼?UI更新?我们的页面状态state
不是已经更新了吗?为啥还要UI更新?
这里有一个非常重要的方法:setState()
。我们先把代码补充完整再说明
componentDidMount() {
// ...
}
tick() {
this.setState({
date: new Date()
})
}
componentWillUnmount() {
// ...
}
setState()
是React触发页面更新的第二个办法,第一个办法开篇即说过,即render()
方法。setState
作用就是通知React检查带状态的组件中是否含有差值。此时react会生成一个虚拟DOM与之前的版本进行对比,只有有必要更新时才会更新。关于 state 与 setState过程 在我的另一篇文章中有详细说明,有兴趣的可以翻过去看看。
为什么不把tick()
方法写到componentDidMount()
中?因为tick()
只是一个普通方法,他不需要在生命周期中触发,也不用自动触发。只要谁调用了触发即可。因此不需要也不能放在生命周期钩子函数中。
现在这个时钟每秒都会走了。整理一下,我们整个计时器代码如下:
class Clock extends React.Component {
constructor(props) {
super(props);
this.state = {date: new Date()};
}
componentDidMount() {
this.timerID = setInterval(
() => this.tick(),
1000
);
}
componentWillUnmount() {
clearInterval(this.timerID);
}
tick() {
this.setState({
date: new Date()
});
}
render() {
return (
<div>
<h1>Hello, world!</h1>
<h2>It is {this.state.date.toLocaleTimeString()}.</h2>
</div>
);
}
}
ReactDOM.render(
<Clock />,
document.getElementById('root')
);
整个流程的执行过程是这样的:
-
当
<Clock />
被传入ReactDOM.render()
时, React 会调用Clock
组件的构造函数。 因为Clock
要显示的是当前时间,所以它将使用包含当前时间的对象来初始化this.state
。我们稍后会更新此状态。 -
然后 React 调用了
Clock
组件的render()
方法。 React 从该方法返回内容中得到要显示在屏幕上的内容。然后,React 然后更新 DOM 以匹配Clock
的渲染输出。 -
当
Clock
输出被插入到 DOM 中完成时,React 调用componentDidMount()
生命周期钩子。在该方法中,Clock
组件请求浏览器设置一个定时器来一次调用tick()
。 -
浏览器会每隔一秒调用一次
tick()
方法。在该方法中,Clock
组件通过setState()
方法并传递一个包含当前时间的对象来安排一个 UI 的更新。通过setState()
, React 得知了组件state
(状态)的变化, 随即再次调用render()
方法,获取了当前应该显示的内容。 这次,render()
方法中的this.state.date
的值已经发生了改变, 从而,其输出的内容也随之改变。React 于是据此对 DOM 进行更新。 -
如果通过其他操作将
Clock
组件从 DOM 中移除了, React 会调用componentWillUnmount()
生命周期钩子, 所以计时器也会被停止。
正确的使用State(状态)
对于setState()
有三件事情是我们应该要知道的
(1)不要直接修改state
真正触发React对比不同版本的虚拟DOM是setState()
方法,直接修改state
页面不会刷新,这一点与原生javascript
区别较大,需要理解。
// 这么做不会触发React更新页面
this.state.comment = 'hello';
// 使用 setState() 代替
this.setState({ comment: 'hello' });
【注意】在组件中,唯一可以初始化分配this.state
的地方就是构造函数constructor(){}
(2)state(状态)更新可能是异步的
React为了优化性能,有可能会将多个setState()
调用合并为一次更新。这就导致 this.props
和this.state
可能是异步更新的,你不能依赖他们的值计算下一个state(状态)
// counter 计数更新会失败
this.setState({
counter: this.state.counter this.props.increment
})
如果我们有这种需求,可以使用以下setState()
办法:
// ES6 箭头函数法
this.setState((prevState, props) => ({
counter: prevState.counter + props.increment
}));
// 常规函数法
this.setState(function(prevState, props) {
return {
counter: prevState.counter + props.increment
};
})
(3)state(状态)更新会被合并
当你调用setState()
, React会你提供的对象合并到当前state状态中。例如,你的状态可能包含几个独立的变量,然后你用几个独立的setState方法去调用更新,如下
constructor(props) {
super(props);
this.state = {
posts: [],
comments: []
};
}
componentDidMount() {
fetchPosts().then(response => {
this.setState({
posts: response.posts
});
});
fetchComments().then(response => {
this.setState({
comments: response.comments
});
});
}
合并是浅合并,所以,this.setState({comments})
在合并过程中不会改变this.state.posts
的值,但是会完全替换this.state.comments
的值。
数据向下流动
无论是作为父组件还是子组件,它都无法或者一个组件是否有状体,同时也不需要关心另一个组件是定义为函数组件还是类组件。这就是为什么state
经常被称为 本地状态 或 封装状态 的原因, 他不能被拥有并设置它的组件以外的任何组件访问。那如果需要访问怎么处理?
(1)作为其子组件的props(属性)
// 在组件中使用
<h2>It is {this.state.date.toLocaleTimeString()}</h2>
// 传递给子组件作为props
<FormattedDate date={this.state.date} />
虽然FormattedDate
组件通过props
接收了date
的值,但它仍然不能获知该值是来自于Clock
的state
, 还是 Clock
的props
, 或者一个手动创建的变量.
这种数据关系,一般称为"从上到下"或"单向"的数据流。任何state(状态)
始终由某个特定组件所有,并且从该state
导出的任何数据 或 UI 只能影响树"下方"的组件
如果把组件树想像为 props(属性)
的瀑布,所有组件的 state(状态)
就如同一个额外的水源汇入主流,且只能随着主流的方向向下流动。
各组件完全独立
借用上文的Clock
组件,我们创建一个App
组件,并在其中渲染三个Clock
:
function App() {
return (
// 之前说过组件只能返回一个根节点,所以用<div>包起来
<div>
<Clock />
<Clock />
<Clock />
</div>
);
}
ReactDOM.render(
<App />,
document.getElementById('root')
);
每个Clock
都设立它自己的计时器并独立更新,如果App
中有一个数据变量,也能被三个Clock
相互独立修改。
至于何时使用有状态组件,何时使用无状态组件,被认为是组件的一个实现细节,取决于你当时的需求,你可以在有状态组件中使用无状态组件,也可以在无状态组件中使用有状态组件
属性扩散
有时候你需要给组件设置多个属性,你不想一个个写下这些属性,或者有时候你甚至不知道这些属性的名称,这时候 spread attributes 的功能就很有用了。
比如:
var props = {};
props.foo = x;
props.bar = y;
var component = <Component {...props} />;
props
对象的属性会被设置成 Component
的属性。
属性也可以被覆盖:
var props = { foo: 'default' };
var component = <Component {...props} foo={'override'} />;
console.log(component.props.foo); // 'override'
写在后面的属性值会覆盖前面的属性。
补充:State 与 Props 区别:
- 组件通过属性(props) 和 状态(state)传递数据
- props 是组件对外的接口,state 是组件对内的接口
上层组件通过下层组件的props属性向下层组件传递数据或方法(state是从上到下单向流动的
,从父级到子元素)
换言之,
下层组件通过 props 属性获取上层组件的数据或方法 - props 是只读的
- state 更新是异步的
五、事件处理
通过 React 元素处理事件跟在 DOM 元素上处理事件非常相似。但是有一些语法上的区别:
- React 事件使用驼峰命名,而不是全部小写
- 通过 JSX , 传递一个函数作为事件处理程序,而不是一个字符串
// html usage
<button onclick="todo()">click me</button>
// React usage
<button onClick={todo}>click me></button>
3.在React中不能通过返回false
来阻止默认行为。必须明确的调用 preventDefault
// html usage
<a href="#" onclick="console.log('clicked'); return false">
Click me
</a>
// React usage
class ActionLink extends React.Component {
function handleClick(e) {
e.preventDefault();
console.log('clicked.');
}
return (
<a href="#" onClick={handleClick}>
Click me
</a>
)
}
在这里,React团队帮Coder们实现了e事件的跨浏览器兼容问题。当使用React时,我们也不需要调用addEventListener
在DOM 元素被创建后添加事件监听器。相反,只要当元素被初始渲染的时候提供一个监听器就可以了。
当使用ES6类定义一个组件时,通常的一个事件处理程序就是类上的一个方法,看个例子,Toggle
组件渲染一个按钮,让用户在 “ON” 和 “OFF” 状态之间切换:
class Toggle extends React.Component {
constructor(props) {
super(props);
this.state = {isToggleOn: true};
// 这个绑定是必要的,使`this`在回调中起作用
this.handleClick = this.handleClick.bind(this);
}
handleClick() {
this.setState(prevState => ({
isToggleOn: !prevState.isToggleOn
}));
}
render() {
return (
<button onClick={this.handleClick}>
{this.state.isToggleOn ? 'ON' : 'OFF'}
</button>
);
}
}
ReactDOM.render(
<Toggle />,
document.getElementById('root')
);
绑定类方法
在JSX回调中你必须注意 this
的指向。 在 JavaScript 中,类方法默认没有 绑定 的。如果你忘记绑定 this.handleClick
并将其传递给onClick
,那么在直接调用该函数时,this
会是 undefined
。
这不是 React 特有的行为;这是 JavaScript
中的函数如何工作的一部分,可以使用属性初始值设置来正确地 绑定(bind
) 回调,但这是实验性做法,不建议使用,以后有可能会废弃,如果你没有使用属性初始化语法
(1)可以在回调中使用一个 arrow functions
class LoginButton extends React.Component {
handleClick() {
console.log('this is: ', this)
}
render() {
// 这个语法确保 `this` 被绑定在 handleClick 中
return (
<button onClick={(e) => this.handleClick(e)}>
Click me
</button>
);
}
}
(2)使用Function.prototype.bind
方法,相对简洁方便
<button onClick={this.handleClick.bind(this)}>
Click me
</button>
传递参数给事件处理程序
在循环内部,通常需要将一个额外的参数传递给事件处理程序,常用的有一下两种方案;
<button onClick={(e) => this.deleteRow(id, e)}>Delete Row</button>
<button onClick={this.deleteRow.bind(this,id)}>Delete Row</button>
上面两个例子中,参数 e
作为 React 事件对象将会被作为第二个参数进行传递。通过箭头函数的方式,事件对象必须显式的进行传递,但是通过 bind
的方式,事件对象以及更多的参数将会被隐式的进行传递。
六、条件渲染
在 React 中,你可以创建不同的组件封装你所需要的行为。然后,只渲染它们之中的一些,取决于你的应用的状态。
整个组件的条件渲染
React 中的条件渲染就可在JS中的条件语句一样,使用JS操作符如if
或者条件控制符来创建渲染当前的元素,并且让React更新匹配的UI。比如我们有一个需求,需要判断用户是否登录,来显示不同组件
function UserGreeting(props) {
return <h1>Welcome back!</h1>
}
function GustGrreeting(props) {
return <h1>Please sign up.</h1>
}
function Greeting(props) {
const isLoggedIn = props.isLoggedIn;
if (isLoggedIn) {
return <UserGreeting />
}
return <GuestGreeting />
}
ReactDOM.render(
<Greeting isLoggedIn={false} />,
document.getElementById('root')
);
使用元素变量条件渲染部分内容
你可以用变量来存储元素。这可以帮助您有条件地渲染组件的一部分,而输出的其余部分不会更改。下方两个组件用于显示登出和登入按钮
function LoginButton() {
return(
<button onClick={props.onClick}>Login</button>
)
}
function LogoutButton(props) {
return (
<button onClick={props.onclick}>Logout</button>
)
}
登入登出按钮已做好,接下来需要实现有切换功能的一个有状态的组件,为了更系统化学习,我们把前面的Greeting
组件一起加进来
class LoginControl extends React.Component {
constructor(props) {
super(props);
this.state = {
isLoginedIn: false
}
}
handleLoginClick() {
this.setState({ isLoggedIn: true });
}
handleLogoutClick() {
this.setState({ isLoggedIn: false });
}
render() {
const isLoggedIn = this.state.isLoggedIn;
let button = null;
if (isLoggedIn) {
button = <LogoutButton onClick={this.handleLogoutClick.bind(this)} />
} else {
button = <LoginButton onclick={this.handleLoginClick.bind(this)} />
}
return (
<div>
<Greeting isLoggedIn={isLoggedIn} />{button}</div>
</div>
)
}
}
reactDOM.render(
<LoginControl />,
document.getElementById('root')
)
使用if
是很常见的一种做法,当然也有一些更简短的语。JSX
中有几种内联条件的方法,
(1)使用逻辑与&&操作符的内联if用法
我们可以在 JSX
中嵌入任何表达式,方法是将其包裹在花括号中,同样适用于JS的逻辑与&&运算符
function Mailbox(props) {
const unreadMessages = props.unreadMessages;
return (
<div>
<h1>Hello!</h1>
{ unreadMeaasges.length > 0 &&
<h2> You have {unreadMessages.length} unread messages.
}
</div>
)
}
cosnt message = ['React', 'Re: React', 'Re:Re: React'];
ReactDOM.render(
<Mailbox unreadMessages={messages} />,
document.getElementById('root')
);
该案例是可以正常运行的,因为在 JavaScript
中, true && expression
总是会评估为 expression
,而 false && expression
总是执行为 false
。并且我们可以在表达式中嵌入表达式
(2)使用条件操作符的内联If-Else(三目运算符)
条件操作符 即三目表达式:condition ? trueExpression : falseExpression
// 条件渲染字符串
<div>The user is {isLoggedIn ? 'currently' : 'not'} logged in.</div>
// 条件渲染组件
<div>
{isLoggedIn ? (
<LogoutButton onClick={this.handleLogoutClick} />
) : (
<LoginButton onClick={this.handleLoginClick} />
)}
</div>
总之,遵循一个原则,哪种方式易于阅读,就选择哪种写法。并且,但条件变得越来越复杂时,可能是提取组件的好时机。
阻止组件渲染
在极少数情况下,您可能希望组件隐藏自身,即使它是由另一个组件渲染的。为此,返回 null
而不是其渲染输出。注意这里是不渲染,不是不显示。
在下面的例子中,根据名为warn
的 props
值,呈现 <WarningBanner />
。如果 props
值为 false
,则该组件不渲染:
function WarningBanner(props) {
if (props.warn) {
return null;
}
return (
<div className="warning">Warning</div>
)
}
class Page extends React.Component {
constructor(props) {
super(props);
this.state = { showWarning: true }
}
handleToggleClick() {
this.setState(prevState => ({
showWarning: !prevState.showWarning
}));
}
render() {
return (
<div>
<Warningbanner warn={this.state.showWarning} />
<button onClick={this.handleToggleClick.bind(this)}>
{ this.state.showWarning ? 'Hide' : 'Show'}
</button>
</div>
)
}
}
ReactDOM.render(
<Page />,
document.getElementById('root')
)
从组件的 render
方法返回 null
不会影响组件生命周期方法的触发。 例如, componentWillUpdate
和componentDidUpdate
仍将被调用。因此需要组件刚载入时就要判断执行返回null