React学习笔记
一、准备工作
- 安装node.js
1、安装react 脚手架
create-react-app安装起来实在是太简单,只需要一条命令,不像别的脚手架,还需要去clone整个脚手架的源码,再在那基础上改。
npm install -g create-react-app
2、构建react项目
create-react-app my-app //项目名称
3、启动react项目
npm start
- 会默认访问localhost:3000端口
4、HelloWorld
- 删除src目录下的所有文件
- 创建一个新的index.js文件
- 在index.js中导包
import React from 'react';
import ReactDOM from 'react-dom';
- 编写程序
ReactDOM.render(
<h1>Hello World</h1>,
document.getElementById('root')
)
二、初探JSX
一种 JavaScript 的语法扩展。 我们推荐在 React 中使用 JSX 来描述用户界面。JSX 乍看起来可能比较像是模版语言,但事实上它完全是在 JavaScript 内部实现的。
function formatName(user){
return user.firstName+' '+user.lastName;
}
const user = {
firstName:'张',
lastName:'san'
}
const element = (
<div tabIndex='0'>
{/*Hello,{formatName(user)}*/} //看起来可能有些奇怪的标签语法既不是字符串也不是 HTML。
{getGreeting(user)}
</div>
)
function getGreeting(user){
if(user){
return <h1>Hello,{formatName(user)}!</h1>
}else{
return <h1>Hello,tester</h1>
}
}
JSX嵌套
- 如果 JSX 标签是闭合式的,那么你需要在结尾处用
/>
, 就好像 XML/HTML 一样:
const element = <img src={user.avatarUrl} />;
- 相互嵌套
const element = (
<div>
<h1>Hello!</h1>
<h2>Good to see you here.</h2>
</div>
);
警告:
因为 JSX 的特性更接近 JavaScript 而不是 HTML , 所以 React DOM 使用
camelCase
小驼峰命名 来定义属性的名称,而不是使用 HTML 的属性名称。
三、元素渲染
- 元素是构成 React 应用的最小单位。
四、组件 & Props
- 组件可以将UI切分成一些独立的、可复用的部件,这样你就只需专注于构建每一个单独的部件。
组件从概念上看就像是函数,它可以接收任意的输入值(称之为“props”),并返回一个需要在页面上展示的React元素。
函数定义/类定义组件
//函数式
/*function Welcome(props) {
return <h1>Hello,{props.name}</h1>
}*/
//ES6 class
class Welcome extends React.Component{
render() {
return <h1>Hello,{this.props.name}</h1>
}
}
const element = <Welcome name='zhangsan'/>
ReactDOM.render(
element,
document.getElementById('root')
)
代码流程
- 我们对
<Welcome name="Sara" />
元素调用了ReactDOM.render()
方法。 - React将
{name: 'Sara'}
作为props传入并调用Welcome
组件。 Welcome
组件将<h1>Hello, Sara</h1>
元素作为结果返回。- React DOM将DOM更新为
<h1>Hello, Sara</h1>
。
警告:
组件名称必须以大写字母开头。
例如,
<div />
表示一个DOM标签,但<Welcome />
表示一个组件,并且在使用该组件时你必须定义或引入它。
组合组件
- 组件可以在它的输出中引用其它组件,这就可以让我们用同一组件来抽象出任意层次的细节。在React应用中,按钮、表单、对话框、整个屏幕的内容等,这些通常都被表示为组件
class Welcome extends React.Component{
render() {
return <h1>Hello,{this.props.name}</h1>
}
}
class App extends React.Component{
render() {
return(
<div>
<Welcome name='zhangsan'/>
<Welcome name='lisi'/>
<Welcome name='wangwu'/>
</div>
)
}
}
ReactDOM.render(
<App/>,
document.getElementById('root')
)
警告:
组件的返回值只能有一个根元素。这也是我们要用一个
<div>
来包裹所有<Welcome />
元素的原因。
- this.props.children表示组件的所有子节点
- React.Children.map可以遍历所有子节点
提取组件
- 可以将组件切分为更小的组件
class Avatar extends React.Component{
render() {
return <img className="Avater"
src={this.props.user.avatarUrl}
alt={this.props.user.name}
/>
}
}
class UserInfo extends React.Component{
render() {
return(
<div className="UserInfo">
<Avatar user={this.props.user}/>
</div>
)
}
}
function Comment(props) {
return(
<div className="Comment">
<UserInfo user={props.author}/>
<div className="Comment-text">
{props.text}
</div>
{<div className="Comment-date">
{formatDate(props.date)}
</div>}
</div>
);
}
Props的只读性
- 无论是使用函数或是类来声明一个组件,它决不能修改它自己的props
纯函数
function sum(a, b) {
return a + b;
}
//没有改变自身的值
非纯函数
function withdraw(account, amount) {
account.total -= amount;
}
//改变了自身
React是非常灵活的,但它也有一个严格的规则:
所有的React组件必须像纯函数那样使用它们的props。
当然,应用的界面是随时间动态变化的,我们将在后面介绍一种称为“state”的新概念,State可以在不违反上述规则的情况下,根据用户操作、网络响应、或者其他状态变化,使组件动态的响应并改变组件的输出。
五、State & 生命周期
- 改写前面时钟的例子
function Clock(props) {
return(
<div>
<h1>Hello,World!</h1>
<h2>{props.data.toTimeString()}</h2>
</div>
)
}
function tick() {
ReactDOM.render(
<Clock data={new Date()}/>,
document.getElementById('root')
);
}
setInterval(tick, 1000);
然而,它错过了一个关键的要求:Clock
设置一个定时器并且每秒更新UI应该是Clock
的实现细节。
理想情况下,我们写一次 Clock
然后它能更新自身:
ReactDOM.render(
<Clock />,
document.getElementById('root')
);
为了实现这个需求,我们需要为Clock
组件添加状态
状态与属性十分相似,但是状态是私有的,完全受控于当前组件。
我们之前提到过,定义为类的组件有一些特性。局部状态就是如此:一个功能只适用于类。
将函数转换为类
可以通过5个步骤将函数组件 Clock
转换为类
- 创建一个名称扩展为
React.Component
的ES6 类 - 创建一个叫做
render()
的空方法 - 将函数体移动到
render()
方法中 - 在
render()
方法中,使用this.props
替换props
- 删除剩余的空函数声明
//创建虚拟dom
class Clock extends React.Component{
constructor(props){
super(props);
this.state = {data:new Date()};
}
//挂载定时器 每1秒调用一次tick
componentDidMount() {
this.timerID = setInterval(
() => this.tick(),
1000
);
}
//卸载定时器
componentWillUnmount() {
clearInterval(this.timerID)
}
tick(){
this.setState({
data:new Date()
});
}
render() {
return(
<div>
<h1>Hello,World!</h1>
<h2>{this.state.data.toTimeString()}</h2>
</div>
)
}
}
//渲染dom
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 知道状态已经改变,并再次调用render()
方法来确定屏幕上应当显示什么。 这一次,render()
方法中的this.state.date
将不同,所以渲染输出将包含更新的时间,并相应地更新DOM。 - 一旦
Clock
组件被从DOM中移除,React会调用componentWillUnmount()
这个钩子函数,定时器也就会被清除。
正确地使用状态
不要直接更新状态
this.state.data = "test" //Wrong 此代码不会重新渲染组件
-----------应该使用setState------------
this.setState({
data:new Date()
});
//构造函数是唯一能够初始化 this.state 的地方。
状态更新可能是异步的
React 可以将多个setState()
调用合并成一个调用来提高性能。
因为 this.props
和 this.state
可能是异步更新的,你不应该依靠它们的值来计算下一个状态。
// Wrong
this.setState({
counter: this.state.counter + this.props.increment,
});
// Correct
this.setState((prevState, props) => ({
counter: prevState.counter + props.increment
}));
// Correct
this.setState(function(prevState, props) {
return {
counter: prevState.counter + props.increment
};
});
状态更新合并
当你调用 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
。
数据自顶向下流动
父组件或子组件都不能知道某个组件是有状态还是无状态,并且它们不应该关心某组件是被定义为一个函数还是一个类。
这就是为什么状态通常被称为局部或封装。 除了拥有并设置它的组件外,其它组件不可访问。
组件可以选择将其状态作为属性传递给其子组件:
- 组件可以选择将其状态作为属性传递给其子组件:
<h2>{this.state.data.toTimeString()}</h2>
- 用于用户定义的组件:
<FormattedDate date={this.state.date} />
FormattedDate
组件将在其属性中接收到 date
值,并且不知道它是来自 Clock
状态、还是来自 Clock
的属性、亦或手工输入:
function FormattedDate(props) {
return <h2>It is {props.date.toLocaleTimeString()}.</h2>;
}
组件的生命周期 三个状态
- Mounting:已插入真实 DOM
- Updating:正在被重新渲染
- Unmounting:已移出真实 DOM
React 为每个状态都提供了两种处理函数,will
函数在进入状态之前调用,did
函数在进入状态之后调用,三种状态共计五种处理函数。
- componentWillMount()
- componentDidMount()
- componentWillUpdate(object nextProps, object nextState)
- componentDidUpdate(object prevProps, object prevState)
- componentWillUnmount()
此外,React 还提供两种特殊状态的处理函数。
- componentWillReceiveProps(object nextProps):已加载组件收到新的参数时调用
- shouldComponentUpdate(object nextProps, object nextState):组件判断是否重新渲染时调用
六、事件处理
React 元素的事件处理和 DOM元素的很相似。但是有一点语法上的不同:
- React事件绑定属性的命名采用驼峰式写法,而不是小写。
- 如果采用 JSX 的语法你需要传入一个函数作为事件处理函数,而不是一个字符串(DOM元素的写法)
传统的 HTML:
<button onclick="activateLasers()">
Activate Lasers
</button>
React 中稍稍有点不同:
<button onClick={activateLasers}>
Activate Lasers
</button>
当你使用 ES6 class 语法来定义一个组件的时候,事件处理器会成为类的一个方法。例如,下面的 Toggle
组件渲染一个让用户切换开关状态的按钮:
class Toggle extends React.Component{
constructor(props){
super(props);
this.state ={isToggleOn:true};
this.handleClick = this.handleClick.bind(this);
}
handleClick(){
this.setState(prevState =>({
isToggleOn: !prevState.isToggleOn
}))
}
render() {
return(
<button onClick={this.handleClick}>
{this.state.isToggleOn?'开':'关'}
</button>
)
}
}
ReactDOM.render(
<Toggle/>,
document.getElementById('root')
)
七、条件渲染
在 React 中,你可以创建不同的组件来封装各种你需要的行为。然后还可以根据应用的状态变化只渲染其中的一部分。
React 中的条件渲染和 JavaScript 中的一致,使用 JavaScript 操作符 if
或条件运算符来创建表示当前状态的元素,然后让 React 根据它们来更新 UI。
function UserGreeting(props) {
return <h1>Welcome userGreeting</h1>
}
function GuestGreeting(props) {
return <h1>Welcome guestGreeting</h1>
}
function Greeting(props) {
const isLoggedIn = props.isLoggedIn;
if(isLoggedIn){
return <UserGreeting/>;
}else {
return <GuestGreeting/>;
}
}
ReactDOM.render(
<Greeting isLoggedIn={false}/>,
document.getElementById('root')
)
- 此示例根据
isLoggedIn
的值渲染不同的问候语。
元素变量
你可以使用变量来储存元素。它可以帮助你有条件的渲染组件的一部分,而输出的其他部分不会更改
//登录成功
function UserGreeting(props) {
return <h1>欢迎进入</h1>
}
//退出
function GuestGreeting(props) {
return <h1>点击登录</h1>
}
function Greeting(props) {
const isLoggedIn = props.isLoggedIn;
if(isLoggedIn){
return <UserGreeting/>;
}else {
return <GuestGreeting/>;
}
}
function LoginButton(props) {
return(
<button onClick={props.onClick}>
Login
</button>
);
}
function LogoutButton(props) {
return(
<button onClick={props.onClick}>
out
</button>
)
}
class LoginControl extends React.Component{
constructor(props){
super(props);
this.handleLogoutClick = this.handleLogoutClick.bind(this);
this.handleLoginClick = this.handleLoginClick.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>
<Greeting isLoggedIn={isLoggedIn}/>
{button}
</div>
)
}
}
ReactDOM.render(
<LoginControl/>,
document.getElementById('root')
)
与运算符 &&
你可以通过用花括号包裹代码在 JSX 中嵌入任何表达式 ,也包括 JavaScript 的逻辑与 &&,它可以方便地条件渲染一个元素。
function Mailbox(props) {
const unreadMessages = props.unreadMessages;
return(
<div>
<h1>Hello!</h1>
{unreadMessages.length>2 &&
<h2>
{unreadMessages.length}
</h2>
}
</div>
);
}
const messages = ['react','sssss','sdswdsf'];
ReactDOM.render(
<Mailbox unreadMessages={messages}/>,
document.getElementById('root')
)
之所以能这样做,是因为在 JavaScript 中,true && expression
总是返回 expression
,而 false && expression
总是返回 false
。
因此,如果条件是 true
,&&
右侧的元素就会被渲染,如果是 false
,React 会忽略并跳过它。
三目运算符
条件渲染的另一种方法是使用 JavaScript 的条件运算符 condition ? true : false
。
function Mailbox(props) {
const unreadMessages = props.unreadMessages;
return(
<div>
<h1>Hello!</h1>
{
unreadMessages.length>2 ? 'yes':'no'
}
</div>
);
}
const messages = ['react','sssss','sdswdsf'];
ReactDOM.render(
<Mailbox unreadMessages={messages}/>,
document.getElementById('root')
)
阻止组件渲染
在极少数情况下,你可能希望隐藏组件,即使它被其他组件渲染。让 render
方法返回 null
而不是它的渲染结果即可实现。
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};
this.handleToggleClick =this.handleToggleClick.bind(this);
}
handleToggleClick(){
this.setState(prevState =>({
showWarning: !prevState.showWarning
}));
}
render() {
return(
<div>
<WarningBanner warn={this.state.showWarning}/>
<button onClick={this.handleToggleClick}>
{this.state.showWarning?'HIDE':'SHOW'}
</button>
</div>
)
}
}
ReactDOM.render(
<Page/>,
document.getElementById('root')
)
八、列表 & Keys
- 列表
const numbers = [1,2,3,4,5];
const ListItem = numbers.map((number) =>
<li>{number}</li>
)
ReactDOM.render(
<ul>
{ListItem}
</ul>,
document.getElementById('root')
)
- 含Key
function ListItem(props) {
return <li>{props.value}</li>
}
function NumberList(props) {
const numbers = props.numbers;
const listItem = numbers.map((number) =>
<ListItem key={number} value={number}/>
);
return(
<ul>
{listItem}
</ul>
)
}
const numbers = [1,2,3,4,5];
ReactDOM.render(
<NumberList numbers={numbers}/>,
document.getElementById('root')
)
元素的key在他的兄弟元素之间应该唯一
数组元素中使用的key在其兄弟之间应该是独一无二的。然而,它们不需要是全局唯一的。当我们生成两个不同的数组时,我们可以使用相同的键
function Blog(props) {
const sidebar = (
<ul>
{props.posts.map((prop) =>
<li key={prop.id}>
{prop.title}
</li>
)}
</ul>
);
const content = props.posts.map((post) =>
<div key={post.id}>
<h3>{post.title}</h3>
<p>{post.context}</p>
</div>
);
return(
<div>
{sidebar}
<hr/>
{content}
</div>
);
}
const posts = [
{id:1,title:"title11111111",context:"111111111111111111111"},
{id:2,title:'title22222222',context: '2222222222222222222'}
];
ReactDOM.render(
<Blog posts={posts}/>,
document.getElementById('root')
)
九、表单
受控组件
当用户提交表单时,HTML的默认行为会使这个表单跳转到一个新页面。在React中亦是如此。但大多数情况下,我们都会构造一个处理提交表单并可访问用户输入表单数据的函数。实现这一点的标准方法是使用一种称为“受控组件”的技术。
在HTML当中,像<input>
,<textarea>
, 和 <select>
这类表单元素会维持自身状态,并根据用户输入进行更新。但在React中,可变的状态通常保存在组件的状态属性中,并且只能用 setState()
方法进行更新。
我们通过使react变成一种单一数据源的状态来结合二者。React负责渲染表单的组件仍然控制用户后续输入时所发生的变化。相应的,其值由React控制的输入表单元素称为“受控组件”。
class NameForm extends React.Component {
constructor(props){
super(props);
//初始化状态
this.state = {value:''};
//绑定this
this.handleSubmit = this.handleSubmit.bind(this);
this.handleChange = this.handleChange.bind(this);
}
handleSubmit(event){
alert(this.state.value);
event.preventDefault();
}
handleChange(event){
this.setState({
value:event.target.value
})
}
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>
)
}
}
ReactDOM.render(
<NameForm/>,
document.getElementById('root')
)
textarea 标签
在React中,<textarea>
会用value
属性来代替。这样的话,表单中的<textarea>
非常类似于使用单行输入的表单:
class EssayForm extends React.Component {
constructor(props){
super(props);
//初始化状态
this.state = {
value:'请输入文本内容!'
}
this.handleSubmit = this.handleSubmit.bind(this);
this.handleChange = this.handleChange.bind(this);
}
handleSubmit(event){
console.log(event);
alert(this.state.value);
event.preventDefault();
}
handleChange(event){
this.setState({
value:event.target.value
})
}
render() {
return(
<form onSubmit={this.handleSubmit}>
<label>
Name:
<textarea value={this.state.value} onChange={this.handleChange}/>
</label>
<input type="submit" value="submit"/>
</form>
)
}
}
ReactDOM.render(
<EssayForm/>,
document.getElementById('root')
)
select 标签
在React中,并不使用之前的selected
属性,而在根select
标签上用value
属性来表示选中项。这在受控组件中更为方便,因为你只需要在一个地方来更新组件。
class FlavorForm extends React.Component {
constructor(props){
super(props);
this.state = {
value:'React'
}
this.handleSubmit = this.handleSubmit.bind(this);
this.handleChange = this.handleChange.bind(this);
}
handleSubmit(event){
alert(this.state.value);
event.preventDefault();
}
handleChange(event){
this.setState({
value:event.target.value
})
}
render() {
return(
<form onSubmit={this.handleSubmit}>
<label>
<select value={this.state.value} onChange={this.handleChange}>
<option value="java">Java</option>
<option value="Scala">Scala</option>
<option value="React">React</option>
<option value="Python">Python</option>
</select>
</label>
<input type="submit" value="submit"/>
</form>
)
}
}
ReactDOM.render(
<FlavorForm/>,
document.getElementById('root')
)
小结:总之,<input type="text">
, <textarea>
, 和 <select>
都十分类似 - 他们都通过传入一个value
属性来实现对组件的控制。
file input 标签
在HTML当中,<input type="file">
允许用户从他们的存储设备中选择一个或多个文件以提交表单的方式上传到服务器上, 或者通过 Javascript 的 File API 对文件进行操作 。
<input type="file" />
由于该标签的 value
属性是只读的, 所以它是 React 中的一个非受控组件。
多个输入的解决方法
当你有处理多个受控的input
元素时,你可以通过给每个元素添加一个name
属性,来让处理函数根据 event.target.name
的值来选择做什么。
class Reservation extends React.Component {
constructor(props) {
super(props);
this.state = {
isGoing:true,
number:2
}
this.handleChange = this.handleChange.bind(this);
}
handleChange(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>
勾选:<input
name="isGoing"
type="checkbox"
checked={this.state.isGoing}
onChange={this.handleChange}/>
</label>
<label>
数据:<input
name="number"
type="number"
value={this.state.number}
onChange={this.handleChange}/>
</label>
</form>
)
}
}
ReactDOM.render(
<Reservation/>,
document.getElementById('root')
)
十、状态提升
使用 react 经常会遇到几个组件需要共用状态数据的情况。这种情况下,我们最好将这部分共享的状态提升至他们最近的父组件当中进行管理。