WebStorm的使用
新建react项目
1、确保安装了node.js
2、创建新项目
3、删除掉新项目中src/文件夹下的所有文件(不要删除整个文件夹)
cd my-app
cd src
# If you're using a Mac or Linux:
rm -f *
4、导入
import React from 'react';//jsx->babel->js
import ReactDOM from 'react-dom';//获取dom节点
5、启动(在my-app目录下)
npm start
React Docs
JSX
const element=<h1>Hello, world!</h1>;
jsx是一个JavaScript的语法扩展,可以很好地描述UI应该呈现出它应有交互的本质形式,具有JavaScript的全部功能。在 JavaScript 代码中将 JSX 和 UI 放在一起时,会在视觉上有辅助作用。它还可以使 React 显示更多有用的错误和警告消息。
在JSX中嵌入表达式
在 JSX 语法中,你可以在大括号内放置任何有效的JavaScript 表达式。
import React from 'react';
import ReactDOM from 'react-dom/client';
const user={
firstName:'Hu',
lastName:'Qingxia',
sex:'female',
}
function formatName(user){
return user.firstName+' '+user.lastName+' - '+user.sex;
}
const name='John';
const element=(//建议将内容包裹在括号中,可以避免遇到自动插入分号的陷阱
<h1>Hello,{formatName(user)} <h2>{name}</h2> </h1>
);
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(element);
JSX 也是一个表达式,在编译之后,JSX 表达式会被转为普通 JavaScript 函数调用,并且对其取值后得到 JavaScript 对象。=>可将JSX赋值给变量,把JSX当作参数传入,以及从函数中返回JSX。
JSX中指定属性
1、使用引号将属性值指定为字符串字面量
2、使用大括号,在属性值中插入一个JavaScript表达式
(对于同一属性不能同时使用这两种符号)
JSX指定子元素
JSX标签里能包含很多子元素
const element=(
<div>
<a href="https://www.baidu.com">link</a>
<h1>Hello,{formatName(user)}</h1>
<h2>{name}</h2>
<img src={user.url} alt="" width="50px" height="50px"/>
</div>
);
JSX 防止注入攻击
JSX 表示对象
元素渲染
元素是构成React应用的最小砖块,描述了你在屏幕上想看到的内容。
与浏览器的 DOM 元素不同,React 元素是创建开销极小的普通对象。React DOM 会负责更新 DOM 来与 React 元素保持一致。(组件由元素构成)
将一个元素渲染为DOM
<div id="root"></div>
“根” DOM 节点,该节点内的所有内容都将由 React DOM 管理
//将元素渲染到根DOM节点
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(element);
更新已渲染的元素
React 元素是不可变对象。一旦被创建,你就无法更改它的子元素或者属性。一个元素就像电影的单帧:它代表了某个特定时刻的 UI。
根据我们已有的知识,更新 UI 唯一的方式是创建一个全新的元素,并将其传入 root.render()
。
//计时器,每秒重现渲染一次
const root = ReactDOM.createRoot(document.getElementById('root'));
function tick(){
const element=(
<div>
<h1>Hello, world!</h1>
<h2>It is {new Date().toLocaleTimeString()}.</h2>
</div>
);
root.render(element);
}
setInterval(tick,1000);
React DOM 会将元素和它的子元素与它们之前的状态进行比较,只会进行必要的更新来使 DOM 达到预期的状态。(共同之处保持不变)
组件&Props
组件允许你将 UI 拆分为独立可复用的代码片段,并对每个片段进行独立构思。从概念上类似于 JavaScript 函数。它接受任意的入参(即 “props”),并返回用于描述页面展示内容的 React 元素。
注意:组件名称必须以大写字母开头,React将小写字母开头的组件视为原生DOM标签,每个组件都是真正独立的。
函数与Class组件
函数组件
function Welcome(props){
return <h1>Hello,{props}</h1>
}
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(Welcome('World!'));
类组件
class Welcome extends React.Component {
render() {
return <h1>Hello, {this.props.name}</h1>;
}
}
渲染组件
React元素可以是DOM标签,也可以是用户自定义的组件。
function Welcome(props){
return <h1>Hello,{props.name}</h1>;
}
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(<Welcome name="Sara"/>);//自定义组件
//React调用Welcome组件,并将{name:'Sara'}作为props传入
//Welcome组件将<h1>Hello,Sara</h1>元素作为返回值
//React高效地将DOM更新
当 React 元素为用户自定义组件时,它会将 JSX 所接收的属性(attributes)以及子组件(children)转换为单个对象传递给组件,这个对象被称之为 “props”。
组合组件
function Welcome(props){
return <h1>Hello,{props.name}</h1>;
}
function APP(){
return(
<div>
<Welcome name="Sara"/>
<Welcome name="Jack"/>
<Welcome name="Amy"/>
</div>
);
}
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(<APP />);
提取组件
什么时候提取组件?
如果 UI 中有一部分被多次使用(Button
,Panel
,Avatar
),或者组件本身就足够复杂(App
,FeedStory
,Comment
),那么它就是一个可提取出独立组件的候选项。
import React from 'react';
import ReactDOM from 'react-dom/client';
function formatDate(date){
return date.toLocaleDateString();
}
function Avatar(props){
return(
<img className="Avatar"
src={props.user.avatarUrl}
alt={props.user.name} />
);
}
function UserInfo(props){
return(
<div className="UserInfo">
<Avatar user={props.user}/>,
<div className="UserInfo-name">
{props.user.name}
</div>
</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>
);
}
const comment={
date: new Date(),
text: 'I hope you enjoy learning React!',
author: {
name: 'Hello Kitty',
avatarUrl: 'logo512.png'
}
}
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
<Comment
author={comment.author}
text={comment.text}
date={comment.date}
/>
);
Props
Props的只读性
纯函数:函数不会尝试更改入参,且多次调用下相同的入参始终返回相同的结果。
所有 React 组件都必须像纯函数一样保护它们的 props 不被更改。
State&生命周期性
State 与 props 类似,但是 state 是私有的,并且完全受控于当前组件。
将函数组件转换成class组件
每次组件更新时 render
方法都会被调用,但只要在相同的 DOM 节点中渲染 <Clock />
,就仅有一个 Clock
组件的 class 实例被创建使用。这就使得我们可以使用如 state 或生命周期方法等很多其他特性。
class Clock extends React.Component{
render() {
return(
<div>
<h1>Hello, world!</h1>
<h2>It is {this.props.date.toLocaleTimeString()}.</h2>
</div>
);
}
}
向class组件中添加局部的state
class Clock extends React.Component{
constructor(props) {//构造函数,将props传递到父类的构造函数中
super(props);
this.state={date: new Date()};
}
render() {
return(
<div>
<h1>Hello, world!</h1>
<h2>It is {this.state.date.toLocaleTimeString()}.</h2>
</div>
);
}
}
将生命周期方法添加到 Class 中
1、挂载:当 Clock
组件第一次被渲染到 DOM 中的时候,就为其设置一个计时器
2、卸载:当 DOM 中 Clock
组件被删除的时候,应该清除计时器。
import React from 'react';
import ReactDOM from 'react-dom';
class Clock extends React.Component{
constructor(props) {
super(props);
this.state = {date: new Date()};//用一个包含当前时间的对象来初始化this.state
}
//生命周期方法
//当 Clock 的输出被插入到 DOM 中后,React 就会调用 ComponentDidMount() 生命周期方法。在这个方法中,Clock 组件向浏览器请求设置一个计时器来每秒调用一次组件的 tick() 方法。
componentDidMount() { //该方法在组件已经被渲染到DOM中后运行,所以最好在这里设置计时器
this.timerID = setInterval(
() => this.tick(),
1000
);
}
//一旦 Clock 组件从 DOM 中被移除,React 就会调用 componentWillUnmount() 生命周期方法,这样计时器就停止了。
componentWillUnmount() {//清除计时器
clearInterval(this.timerID);
}
tick(){
this.setState({ //更新组件state,React得知state已经改变,就会重新调用render()方法,相应的更新DOM
date:new Date()
});
}
render() {
return(
<div>
<h1>Hello, world!</h1>
<h2>It is {this.state.date.toLocaleTimeString()}.</h2>
</div>
);
}
}
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(<Clock />);//React先调用Clock组件的构造函数(初始化),然后调用组件的render()方法,最后更新DOM来匹配Clock渲染的输出
正确的使用state
1、不要直接修改state,而要使用setState( );
构造函数是唯一可以给this.state赋值的地方。
2、state的更新可能是异步的
因为 this.props
和 this.state
可能会异步更新,所以你不要依赖他们的值来更新下一个状态。
3、state的更新会被合并
数据是向下流动的
不管是父组件或是子组件都无法知道某个组件是有状态的还是无状态的,并且它们也并不关心它是函数组件还是 class 组件。
这就是为什么称 state 为局部的或是封装的的原因。除了拥有并设置了它的组件,其他组件都无法访问。
组件可以选择把它的 state 作为 props 向下传递到它的子组件中
任何的 state 总是所属于特定的组件,而且从该 state 派生的任何数据或 UI 只能影响树中“低于”它们的组件。如果你把一个以组件构成的树想象成一个 props 的数据瀑布的话,那么每一个组件的 state 就像是在任意一点上给瀑布增加额外的水源,但是它只能向下流动。
事件处理
-
React 事件的命名采用小驼峰式(camelCase),而不是纯小写。
-
使用 JSX 语法时你需要传入一个函数作为事件处理函数,而不是一个字符串。
React中,你不能通过返回
false
的方式阻止默认行为。你必须显式的使用preventDefault
function Form(){ function handleSubmit(e){//e为事件对象 e.preventDefault();//阻止默认操作,preventDefault它是事件对象(Event)的一个方法,作用是取消一个目标元素的默认行为 //form表单如果 type 属性是 “submit”,在事件传播的任意阶段可以调用任意的事件句柄,通过调用该方法,可以阻止表单的默认提交行为。 console.log('You clicked submit.'); } return( <form onSubmit={handleSubmit}> <button type="submit">Submit</button> </form> ); }
-
使用 React 时,你一般不需要使用
addEventListener
为已创建的 DOM 元素添加监听器。事实上,你只需要在该元素初始渲染的时候添加监听器即可。在传统HTML写法里,οnclick=“handleClickBtn()”,引号内部是js代码,当触发事件后执行该脚本;
<button onclick="handleClickBtn()">按钮</button>
react里使用{ }JSX的语法,onClick={handleClickBtn}这句代码React会帮我们添加监听器,handleClickBtn是当前作用域下的一个指向方法的变量名,但是由于JS函数工作原理问题,在ES6 class语法定义组件中(函数组件则不需要,但它也不能提供状态功能的支持,本文仅讨论class组件),我们需要为它绑定this,否则handleClickBtn内部this的指向会出现异常,指向该函数的调用者(此处是undefined)
<button onClick={handleClickBtn}>按钮</button>
-
当你使用 ES6 class 语法定义一个组件的时候,通常的做法是将事件处理函数声明为 class 中的方法
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>
);
}
}
通常情况下,如果你没有在方法后面添加 ()
,例如 onClick={this.handleClick}
,你应该为这个方法绑定 this
。
函数只要是要调用它进行执行的,都必须加括号。此时,函数()实际上等于函数的返回值。当然,有些没有返回值,但已经执行了函数体内的行为,这个是根本,就是说,只要加括号的,就代表将会执行函数体代码。
不加括号的,都是把函数名称作为函数的指针,用于传参,此时不是得到函数的结果,因为不会运行函数体代码。它只是传递了函数体所在的地址位置,在需要的时候好找到函数体去执行。
解决方法
-
箭头函数
箭头函数内this是词法作用域,由上下文确定
通过
箭头函数
调动我们的事件处理方法,避免了this
指向异常。class LoggingButton extends React.Component{ handleClick(){ console.log('this is: ',this); } render() { return( <button onClick={()=>this.handleClick()}> Click me </button> ); } }
但是每次渲染组件都会创建不同的回调函数,少数情况下(该回调函数作为prop传入子组件时),可能会进行额外的重新渲染。
-
实验性
public class fields
语法将事件处理函数的实现通过箭头函数赋值
class LoggingButton extends React.Component{ // 此语法确保 `handleClick` 内的 `this` 已被绑定。 // 注意: 这是 *实验性* 语法。 handleClick=()=>{ console.log('this is: ',this); } render() { return( <button onClick={this.handleClick}> Click me </button> ); } }
-
向事件处理程序传递参数
条件渲染
在 React 中,你可以创建不同的组件来封装各种你需要的行为。然后,依据应用的不同状态,你可以只渲染对应状态下的部分内容。
function UserGreeting(props) {
return <h1>Welcome back!</h1>;
}
function GuestGreeting(props) {
return <h1>Please sign up.</h1>;
}
function Greeting(props){
const isLoggedIn= props.isLoggedIn;
if(isLoggedIn){//根据isLoggedIn的值来渲染不同的问候语
return <UserGreeting />;
}
return <GuestGreeting />;
}
元素变量
使用变量来储存元素。 它可以帮助你有条件地渲染组件的一部分,而其他的渲染部分并不会因此而改变
function LoginButton(props) {
return (
<button onClick={props.onClick}>
Login
</button>
);
}
function LogoutButton(props) {
return (
<button onClick={props.onClick}>
Logout
</button>
);
}
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;
if(isLoggedIn){ //有条件的渲染部分组件的一部分
button= <LogoutButton onClick={this.handleLogoutClick}/>
}else{
button=<LoginButton onClick={this.handleLoginClick}/>
}
return(
<div>
<Greeting isLoggedIn={isLoggedIn}/>
{button}
</div>
);
}
}
内联条件渲染
与运算符&&
如果条件是 true
,&&
右侧的元素就会被渲染,如果是 false
,React 会忽略并跳过它。
请注意,返回 false 的表达式会使 &&
后面的元素被跳过,但会返回 false 表达式
function 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'];
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(<Mailbox unreadMessages={messages} />);
//上例中button也可写成这样
{isLoggedIn && <LogoutButton onClick={this.handleLogoutClick}/>}
{!isLoggedIn && <LoginButton onClick={this.handleLoginClick}/>}
三目运算符
{isLoggedIn
? <LogoutButton onClick={this.handleLogoutClick} />
: <LoginButton onClick={this.handleLoginClick} />
}
阻止组件渲染
在极少数情况下,你可能希望能隐藏组件,即使它已经被其他组件渲染。若要完成此操作,你可以让 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(state => ({
showWarning: !state.showWarning
}));
}
render() {
return (
<div>
<WarningBanner warn={this.state.showWarning} />
<button onClick={this.handleToggleClick}>
{this.state.showWarning ? 'Hide' : 'Show'}
</button>
</div>
);
}
}
在组件的 render
方法中返回 null
并不会影响组件的生命周期。例如,上面这个示例中,componentDidUpdate
依然会被调用。
列表&Key
渲染多个组件
使用 Javascript 中的 map()
方法来遍历 numbers
数组。将数组中的每个元素变成 <li>
标签,最后我们将得到的数组赋值给 listItems
const numbers = [1, 2, 3, 4, 5];
const listItems = numbers.map((numbers) =>
<li>{numbers}</li>
);
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(<ul>{listItems}</ul>);//将整个listItems插入到<ul>中
基础列表组件
function NumberList(props){
const numbers=props.numbers;
const listItems=numbers.map(
(number)=><li>{number}</li>
);
return(
<ul>{listItems}</ul>
);
}
const numbers=[1,2,3,4,5];
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(<NumberList numbers={numbers}/>);
当我们运行这段代码,将会看到一个警告 a key should be provided for list items
,意思是当你创建一个元素时,必须包括一个特殊的 key
属性。
需要给每个列表元素分配一个Key属性来解决问题。
const listItems=numbers.map((number)=>
<li key={number.toString()}>
{number}
</li>
);
Key
key 帮助 React 识别哪些元素改变了,比如被添加或删除。因此你应当给数组中的每一个元素赋予一个确定的标识。
一个元素的 key 最好是这个元素在列表中拥有的一个独一无二的字符串。当元素没有确定 id 的时候,万不得已你可以使用元素索引 index 作为 key。
const listItems=numbers.map((number,index)=>
<li key={index}>
{number}
</li>
);
但如果列表项目的顺序可能会变化,不建议使用索引来用作 key 值,因为这样做会导致性能变差,还可能引起组件状态的问题。如果你选择不指定显式的 key 值,那么 React 将默认使用索引用作为列表项目的 key 值。
用key提取组件
元素的 key 只有放在就近的数组上下文中才有意义。
const numbers=[1,2,3,4,5];
function ListItem(props){
return <li>{props.value}</li>
}
function NumberList(props){
const numbers=props.numbers;
const listItems=numbers.map((number)=> //在JSX中嵌入map
//提取组件,key 应该在数组的上下文中被指定
<ListItem key={number.toString()} value={number}/>
);
return(
<ul>{listItems}</ul>
);
}
一个好的经验法则是:在 map()
方法中的元素需要设置 key 属性。
Key值在兄弟节点之间必须唯一
数组元素中使用的 key 在其兄弟节点之间应该是独一无二的。然而,它们不需要是全局唯一的。当我们生成两个不同的数组时,我们可以使用相同的 key 值。
key 会传递信息给 React ,但不会传递给你的组件。如果你的组件中需要使用 key
属性的值,请用其他属性名显式传递这个值。
const content = posts.map((post) =>
<Post
key={post.id}
id={post.id}//Post组件可以读出props.id,但是不能读出props.key
title={post.title} />
);
表单
在 React 里,HTML 表单元素的工作方式和其他的 DOM 元素有些不同,这是因为表单元素通常会保持一些内部的 state。
受控组件
在 HTML 中,表单元素(如<input>
、 <textarea>
和 <select>
)通常自己维护 state,并根据用户输入进行更新。在 React 中,可变状态(mutable state)通常保存在组件的 state 属性中,并且只能通过使用 setState()
来更新。
把两者结合起来,使 React 的 state 成为“唯一数据源”。渲染表单的 React 组件还控制着用户输入过程中表单发生的操作。被 React 以这种方式控制取值的表单输入元素就叫做“受控组件”。
//受控组件
//对于受控组件来说,输入的值始终由 React 的 state 驱动。
class NameForm extends React.Component{
constructor(props) {
super(props);
this.state={
name:'',
txt:'请撰写一篇关于你喜欢的 DOM 元素的文章.',
food:'mango'
}
}
handleChange=(event)=>{
this.setState({
name:event.target.name,
txt:event.target.txt,
food:event.target.food
})
}
handleSubmit=(event)=>{ //提交时打印名字
alert('提交的名字: '+this.state.value);
event.preventDefault();
}
render() {
return(
<form onSubmit={this.handleSubmit}>
<label>
名字:
<input type="text" value={this.state.value} onChange={this.handleChange}/>
</label>
<label>
<!--在 React 中,<textarea> 使用 value 属性代替-->
文章:
<textarea value={this.state.txt} onChange={this.handleChange}/>
</label>
<label>
<!--在根 select 标签上使用 value 属性-->
食物:
<select value={this.state.food} onChange={this.handleChange}>
<option value="apple">苹果</option>
<option value="mango">芒果</option>
<option value="coconut">椰子</option>
</select>
</label>
<input type="submit" value="提交"/>
</form>
);
}
}
非受控组件
文件input标签
<input type="file">
允许用户从存储设备中选择一个或多个文件,将其上传到服务器,或通过使用 JavaScript 的 File API 进行控制。
因为它的 value 只读,所以它是 React 中的一个非受控组件。
处理多个输入
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 //当点击checkbox时,name的值为isGoing,然后isGoing的值会被改成value(是否被选中)
});
}
render() {
return (
<form>
<label>
参与:
<input
name="isGoing"
type="checkbox"
checked={this.state.isGoing}
onChange={this.handleInputChange} />
</label>
<br />
<label>
来宾人数:
<input
name="numberOfGuests"
type="number"
value={this.state.numberOfGuests}
onChange={this.handleInputChange} />
</label>
</form>
);
}
}