一、 搭建react应用
方式有两种
1.脚手架生成
npx create-react-app my-app
cd my-app
npm start
等同于
npm create-react-app
create-react-app my-app
2.在html页面中,通过CDN引入
//开发环境
<script crossorigin src="https://unpkg.com/react@16/umd/react.development.js"></script>
<script crossorigin src="https://unpkg.com/react-dom@16/umd/react-dom.development.js"></script>
//生产环境(React的缩小和优化生产版)
<script crossorigin src="https://unpkg.com/react@16/umd/react.production.min.js"></script>
<script crossorigin src="https://unpkg.com/react-dom@16/umd/react-dom.production.min.js"></script>
二、在项目中如何使用React
1.元素渲染
元素是构成React 应用的最小单位。
元素用来描述你在屏幕上看到的内容:
const element = <h1>Hello, world</h1>;
与浏览器的DOM 元素不同,React 当中的元素事实上是普通的对象,React DOM 可以确保浏览器DOM 的数据内容与React 元素保持一致。
注:元素和组件是有区别的
实现
通过调用ReactDOM.render()的方法来将元素渲染到页面上:
<!--html-->
<div id="root"></div>
<!--js/type="text/bable"-->
const element = <h1>Hello, world</h1>;
ReactDOM.render(element, document.getElementById('root'));
更新元素渲染
React 元素都是不可变的。当元素被创建之后,你是无法改变其内容或属性的。目前,更新界面的唯一办法是创建一个新的元素,然后将它传入 ReactDOM.render()
方法:
function tick() {
const element = (
<div>
<h1>Hello, world!</h1>
<h2>It is {new Date().toLocaleTimeString()}.</h2>
</div>
);
ReactDOM.render(
element,
document.getElementById('root')
);
}
setInterval(tick, 1000);
拓展
const element = <h1>Hello, world!</h1>;
这种语法被称为JSX, 一种JavaScript 的语法扩展。React 中推荐使用JSX 来描述用户界面。JSX 乍看起来可能比较像是模版语言,但事实上它完全是在JavaScript 内部实现的。
关于JSX
- 可以任意地在JSX当中使用JavaScript表达式,但表达式要包含在大括号里。
const userName= 'Danny';
const element = (<h1>Hello, {userName}!</h1>);
-
JSX 本身其实也是一种表达式
编译后,JSX 会被转化为普通的JavaScript 对象。所以可以在if或者for语句里使用JSX,将它赋值给变量,当作参数传入,或作为返回值。 -
JSX 属性
可以使用引号来定义以字符串为值的属性:
const element = <div tabIndex="0"></div>;
也可以使用大括号来定义以JavaScript 表达式为值的属性:
const element = <img src={user.avatarUrl}></img>;
注:因为JSX的特性更接近JavaScript而不是HTML ,所以React DOM使用camelCase
小驼峰命名来定义属性的名称,而不是使用HTML的属性名称。例如,class
-className
,tabindex
-tabIndex
。 -
JSX标签可以嵌套
const element = (
<div>
<h1>Hello!</h1>
<h2>Good to see you here.</h2>
</div>
);
- JSX 代表Objects
Babel转译器会把JSX转换成一个名为React.createElement()的方法调用。
const element = (
<h1 className="greeting">
Hello, world!
</h1>
);
等价于
const element = React.createElement(
'h1',
{className: 'greeting'},
'Hello, world!'
);
React.createElement() 这个方法首先会进行一些避免bug的检查,之后会返回一个类似下面例子的对象:
// 注意: 以下示例是简化过的(不代表在 React 源码中是这样)
const element = {
type: 'h1',
props: {
className: 'greeting',
children: 'Hello, world'
}
};
这样的对象被称为“React 元素”。它代表所有你在屏幕上看到的东西。React 通过读取这些对象来构建DOM 并保持数据内容一致。
2.组件& Props
组件可以将UI切分成一些独立的、可复用的部件,这样你就只需专注于构建每一个单独的部件。
组件从概念上看就像是函数,它可以接收任意的输入值(称之为“props”),并返回一个需要在页面上展示的React元素。
定义组件有两种方式
- 函数定义组件
- (ES6)类定义组件(类组件有一些额外特性)
定义一个组件最简单的方式是使用JavaScript函数:
function Welcome(props) {
return <h1>Hello, {props.name}</h1>;
}
该函数是一个有效的React组件,它接收一个单一的“props”对象并返回了一个React元素。
你也可以使用ES6 class来定义一个组件:
class Welcome extends React.Component {
render() {
return <h1>Hello, {this.props.name}</h1>;
}
}
组件渲染
React元素除了是DOM标签外,也可以是用户自定义的组件,
当React遇到的元素是用户自定义的组件,它会将JSX属性作为单个对象(props)传递给该组件。
function Welcome(props) {
return <h1>Hello, {props.name}</h1>;
}
const element = <Welcome name="Sara" />;
ReactDOM.render(
element,
document.getElementById('root')
);
渲染结果:
Hell,Sara
注:组件名称必须以大写字母开头。
组合组件
组件可以在它的输出中引用其它组件
function Welcome(props) {
return <h1>Hello, {props.name}</h1>;
}
function App() {
return (
<div>
<Welcome name="Sara" />
<Welcome name="Cahal" />
<Welcome name="Edite" />
</div>
);
}
ReactDOM.render(
<App />,
document.getElementById('root')
);
注:组件的返回值只能有一个根元素
Props的只读性
无论是使用函数或是类来声明一个组件,它决不能修改它自己的props。
3.State & 生命周期
生命周期.png之前通过重新调用ReactDOM.render() 方法更新UI
function tick() {
const element = (
<div>
<h1>Hello, world!</h1>
<h2>It is {new Date().toLocaleTimeString()}.</h2>
</div>
);
ReactDOM.render(
element,
document.getElementById('root')
);
}
setInterval(tick, 1000);
接下来学习如何真正实现Clock组件可重用和封装
1. 封装时钟
function Clock(props) {
return (
<div>
<h1>Hello, world!</h1>
<h2>It is {props.date.toLocaleTimeString()}.</h2>
</div>
);
}
function tick() {
ReactDOM.render(
<Clock date={new Date()} />,
document.getElementById('root')
);
}
setInterval(tick, 1000);
2. 将函数转换为类
使用类就允许我们使用其它特性,例如局部状态、生命周期钩子
- 状态与属性十分相似,但是状态是私有的,完全受控于当前组件。且局部状态功能只适用于类。
- 生命周期钩子只适用于类
/*5个步骤将函数组件 `Clock` 转换为类
1. 创建一个名称扩展为 `React.Component` 的ES6 类
2. 创建一个叫做`render()`的空方法
3. 将函数体移动到 `render()` 方法中
4. 在 `render()` 方法中,使用 `this.props` 替换 `props`
5. 删除剩余的空函数声明*/
class Clock extends React.Component {
render() {
return (
<div>
<h1>Hello, world!</h1>
<h2>It is {this.props.date.toLocaleTimeString()}.</h2>
</div>
);
}
}
3. 为Clock组件添加状态
/*1. 在 `render()` 方法中使用 `this.state.date` 替代 `this.props.date`
2. 添加一个类构造函数来初始化状态 `this.state`
3. 从 `<Clock />` 元素移除 `date` 属性*/
class Clock extends React.Component {
constructor(props) {
super(props);
this.state = {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')
);
4. 为组件添加生命周期函数
在组件类上通过声明周期钩子,做一些特定的事
如,组件挂载时(componentDidMount)生成定时器,卸载时(componentWillUumount)清除定时器
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')
);
正确的使用状态
1. 不要直接更新状态
不能直接更新状态,应当使用 setState(),且构造函数是唯一能够初始化 this.state 的地方。
// Wrong(此代码不会重新渲染组件)
this.state.comment = 'Hello';
// Correct
this.setState({comment: 'Hello'});
2. 状态更新可能是异步的
用第二种形式的 setState() 来接受一个函数而不是一个对象。 该函数将接收先前的状态作为第一个参数,将此次更新被应用时的props做为第二个参数:
// Correct
this.setState((prevState, props) => ({
counter: prevState.counter + props.increment
}));
3.状态更新合并
当你调用 setState() 时,React 将你提供的对象合并到当前状态。
//你的状态可能包含一些独立的变量:
constructor(props) {
super(props);
this.state = {
posts: [],
comments: []
};
}
//你可以调用 setState() 独立地更新它们:
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。
4.数据自顶向下流动
3.事件处理
React 元素的事件处理和 DOM元素的很相似。但是有一点语法上的不同:
- React事件绑定属性的命名采用驼峰式写法,而不是小写。
- 如果采用 JSX 的语法你需要传入一个函数作为事件处理函数,而不是一个字符串(DOM元素的写法)
//传统的 HTML:
<button onclick="activateLasers()">
Activate Lasers
</button>
//React 中稍稍有点不同:
<button onClick={activateLasers}>
Activate Lasers
</button>
在 React 中另一个不同是你不能使用返回 false 的方式阻止默认行为。你必须明确的使用 preventDefault。
//传统的 HTML 中阻止链接默认打开一个新页面,你可以这样写:
<a href="#" onclick="console.log('The link was clicked.'); return false">
Click me
</a>
// React,应该这样来写:
function ActionLink() {
function handleClick(e) {
e.preventDefault();
console.log('The link was clicked.');
}
return (
<a href="#" onClick={handleClick}>
Click me
</a>
);
}
注:必须谨慎对待 JSX 回调函数中的 this
,类的方法默认是不会绑定this
的。未绑定时值会是 undefined
。
通常建议在构造函数中绑定或使用属性初始化器语法来避免这类性能问题
向事件处理程序传递参数
//arrow functions
<button onClick={(e) => this.deleteRow(id, e)}>Delete Row</button>
等价
//Function.prototype.bind
<button onClick={this.deleteRow.bind(this, id)}>Delete Row</button>
上面两个例子中,参数 e 作为 React 事件对象将会被作为第二个参数进行传递。通过箭头函数的方式,事件对象必须显式的进行传递,但是通过 bind 的方式,事件对象以及更多的参数将会被隐式的进行传递。
值得注意的是,通过 bind 方式向监听函数传参,在类组件中定义的监听函数,事件对象 e 要排在所传递参数的后面,例如:
class Popper extends React.Component{
constructor(){
super();
this.state = {name:'Hello world!'};
}
preventPop(name, e){ //事件对象e要放在最后
e.preventDefault();
alert(name);
}
render(){
return (
<div>
<p>hello</p>
{/* Pass params via bind() method. */}
<a href="https://reactjs.org" onClick={this.preventPop.bind(this,this.state.name)}>Click</a>
</div>
);
}
}
4.条件渲染
在 React 中,你可以创建不同的组件来封装各种你需要的行为。然后还可以根据应用的状态变化只渲染其中的一部分。
元素变量
你可以使用变量来储存元素。它可以帮助你有条件的渲染组件的一部分,而输出的其他部分不会更改。
class LoginControl extends React.Component {
constructor(props) {
super(props);
this.handleLoginClick = this.handleLoginClick.bind(this);
this.handleLogoutClick = this.handleLogoutClick.bind(this);
this.state = {isLoggedIn: 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} />;
} else {
button = <LoginButton onClick={this.handleLoginClick} />;
}
return (
<div>
...
{button}
</div>
);
}
}
ReactDOM.render(
<LoginControl />,
document.getElementById('root')
);
与运算符 &&
在 JavaScript 中,true && expression 总是返回 expression,而 false && expression 总是返回 false
unction Mailbox(props) {
const unreadMessages = props.unreadMessages;
return (
<div>
<h1>Hello!</h1>
{unreadMessages.length > 0 &&
<h2>
You have {unreadMessages.length} unread messages.
</h2>
}
</div>
);
}
const messages = ['React', 'Re: React', 'Re:Re: React'];
ReactDOM.render(
<Mailbox unreadMessages={messages} />,
document.getElementById('root')
);
三目运算符
阻止组件渲染
在极少数情况下,你可能希望隐藏组件,即使它被其他组件渲染。让 render 方法返回 null 而不是它的渲染结果即可实现。
列表 & Keys
渲染多个组件
你可以通过使用{}
+ map()函数在JSX内构建一个元素集合
function NumberList(props) {
const numbers = props.numbers;
const listItems = numbers.map((number) =>
<li key={number.toString()}>{number}</li>
);
return (
<ul>{listItems}</ul>
);
}
const numbers = [1, 2, 3, 4, 5];
ReactDOM.render(
<NumberList numbers={numbers} />,
document.getElementById('root')
);
Keys
Keys可以在DOM中的某些元素被增加或删除的时候帮助React识别哪些元素发生了变化。因此你应当给数组中的每一个元素赋予一个确定的标识。
5.表单
HTML表单元素与React中的其他DOM元素有所不同,因为表单元素生来就保留一些内部状态。
受控组件
在HTML当中,像<input>
,<textarea>
, 和 <select>
这类表单元素会维持自身状态,并根据用户输入进行更新。但在React中,可变的状态通常保存在组件的状态属性中,并且只能用 setState()
方法进行更新。
值由React控制的输入表单元素称为“受控组件”
input标签
textarea 标签
在HTML当中,<textarea> 元素通过子节点来定义它的文本内容;在React中,<textarea>会用value属性来代替。这样的话,表单中的<textarea> 非常类似于使用单行输入的表单
class NameForm extends React.Component {
constructor(props) {
super(props);
this.state = {value: ''};
this.handleChange = this.handleChange.bind(this);
this.handleSubmit = this.handleSubmit.bind(this);
}
handleChange(event) {
this.setState({value: event.target.value});
}
handleSubmit(event) {
alert('A name was submitted: ' + this.state.value);
event.preventDefault();
}
render() {
return (
<form onSubmit={this.handleSubmit}>
<label>
Name:
<input type="text" value={this.state.value} onChange={this.handleChange} />
</label>
<input type="submit" value="Submit" />
</form>
);
}
}
select 标签
HTML中通过selected属性表示被选中项。在React中,并不使用之前的selected属性,而在根select标签上用value属性来表示选中项。
多个输入的解决方法
当你有处理多个受控的input元素时,你可以通过给每个元素添加一个name属性,来让处理函数根据 event.target.name的值来选择做什么。
class Reservation extends React.Component {
constructor(props) {
super(props);
this.state = {
isGoing: true,
numberOfGuests: 2
};
this.handleInputChange = this.handleInputChange.bind(this);
}
handleInputChange(event) {
const target = event.target;
const value = target.type === 'checkbox' ? target.checked : target.value;
const name = target.name;
this.setState({
[name]: value
});
}
render() {
return (
<form>
<label>
Is going:
<input
name="isGoing"
type="checkbox"
checked={this.state.isGoing}
onChange={this.handleInputChange} />
</label>
<br />
<label>
Number of guests:
<input
name="numberOfGuests"
type="number"
value={this.state.numberOfGuests}
onChange={this.handleInputChange} />
</label>
</form>
);
}
}
6.状态提升
使用 react 经常会遇到几个组件需要共用状态数据的情况。这种情况下,我们最好将这部分共享的状态提升至他们最近的父组件当中进行管理。
网友评论