React基础
1 起步
1.1 上手
npm i -g create-react-app
安装官方脚手架create-react-app demo
初始化
1.2 文件结构
1.3 启动项目
在package.jsom
中可以看到如下一段内容
"scripts": {
"start": "react-scripts start",
"build": "react-scripts build",
"test": "react-scripts test",
"eject": "react-scripts eject"
}
在控制台中cd到项目根目录下输入启动指令(这里以NPM管理工具为例yarn同理)
npm run start
在本地启动一个服务器 预览项目 默认会在http://localhost:3000
地址下运行npm run build
项目打包编译npm run test
在交互式监视模式下启动测试运行程序npm run eject
!!!注意:这是单向操作。一旦你弹出,你就不能回去了。
eact-scripts 是 create-react-app 的一个核心包,一些脚本和工具的默认配置都集成在里面,而 yarn eject 命令执行后会将封装在 create-react-app 中的配置全部反编译到当前项目,这样用户就能完全取得 webpack 文件的控制权。所以,eject 命令存在的意义就是更改 webpack 配置存在的,如果有此类需要请自行理解下方react提供的原文解释,但是在通常的开发项目当中我们并不会使用到。
If you aren’t satisfied with the build tool and configuration choices, you can eject at any time. This command will remove the single build dependency from your project.
Instead, it will copy all the configuration files and the transitive dependencies (webpack, Babel, ESLint, etc) right into your project so you have full control over them. All of the commands except eject will still work, but they will point to the copied scripts so you can tweak them. At this point you’re on your own.
You don’t have to ever use eject. The curated feature set is suitable for small and middle deployments, and you shouldn’t feel obligated to use this feature. However we understand that this tool wouldn’t be useful if you couldn’t customize it when you are ready for it.
2 React启动流程
为了更好的理解React的启动流程我们通常建议新手删除 src
文件下的所有内容,自己手写构建出一个新的React应用。
当然你如果你删除了src
文件下的所有内容自然会报出一个错误即:
Could not find a required file.
Name: index.js
Searched in: C:\Users\23140\Desktop\reactCli\poi\src
没有找到index.js文件,也就是说index.js
是React主要的入口文件我们在src下新建index.js
并且写如下代码
src -> index.js
// React项目的入口文件
import React from "react"; //处理逻辑相关
import ReactDOM from "react-dom" //处理DOM相关 注意react-dom依赖于react所以必须先引入react
// 引入完成后 ReactDOM 会抛出一个rende()方法
// rende()有两个参数 第一个参数为JSX对象,第二个参数为插入到哪里
ReactDOM.render(<h1>Hello React</h1>,document.querySelector('#root'));
// 注意第一个参数并非是HTML标签而是一个JSX对象这个会在下面解释,至于为什么第二个参数要插入到ID为root的标签下可以在public->index.html中找到答案
3 JSX介绍和模板语法渲染
3.1 什么是JSX
JSX = JavaScript + XML
JavaScript 能看到这里大家都很熟悉是什么不必多说
HTML是XML中的一种 所以现阶段可以把XML简单的认为XML是JS与HTML的一个整合
import React from "react";
import ReactDOM from "react-dom";
const ele = <h1>Hello React</h1>;
console.log(ele); //在JS中直接写HTML语法,打印结果是一个对象,这种对象我们称之为虚拟DOM元素或者JSX对象
ReactDOM.render(ele, document.querySelector("#root"));
第四行console输出结果:
3.2模板语法
在React中我们使用{}进行插值例如这样
...
const ele = <h1>Hello {'F_picacho'}</h1>;
ReactDOM.render(ele, document.querySelector("#root"));
嗯这点跟Vue的双大括号插值很像,让我们试试能不能进行表达式运算例如这样
...
const ele = <h1>Hello {221+2}</h1>;
ReactDOM.render(ele, document.querySelector("#root"));
或者说我像要插入一个格式化名字的函数
import React from "react";
import ReactDOM from "react-dom";
const user = {
fristName: "F",
lastName: "picacho"
};
function formatName(user) {
return user.fristName + user.lastName;
}
const ele = <h1>Hello {formatName(user)}</h1>;
ReactDOM.render(ele, document.querySelector("#root"));
当然你也可以在某个方法里返回一个JSX对象或者直接使用三元运算做判断这里就不一一展开了,你能想到的都可以插。!!!但是注意 render只能接受一个闭合标签这点跟Vue一样所以你这个模板中拥有多个标签必须在最外层套一个标签组织成一个标签闭合例如:
import React from "react";
import ReactDOM from "react-dom";
const ele = (
<div>
<h1>Hello World</h1>
<h2>Hello React</h2>
<h3>Hello Vue</h3>
</div>
);
ReactDOM.render(ele, document.querySelector("#root"));
3.3 元素渲染
元素是构成React应用的最小砖块例如:
const ele = <h1>Hello {'F_picacho'}</h1>;
与浏览器的DOM元素不同,React元素是创建开销极小的普通对象。ReactDOM会负责更新DOM来与React元素保持一致。
ReactDOM.render()
其实就是在渲染DOM节点
3.1 React只更新他需要更新的部分
这里写了一个计时器的例子
import React from "react";
import ReactDOM from "react-dom";
function tick() {
const ele = (
<div>
<h1>当前的事件是:</h1>
<h2>{new Date().toLocaleTimeString()}</h2>
</div>
);
ReactDOM.render(ele, document.querySelector("#root"));
}
setInterval(tick, 1000);
我们可以看到React只为我们更新了我们需要更新的部分。
4 循环绑定&过滤元素
4.1循环
4.1.1循环数组
在列表中显示数组元素肯定要用到循环 至于为什么不用foreach
是因为foreach无法返回而map
可以进行返回的操作我们要循环出的对象是li
所以就插值利用map
取到数组中的元素然后返回对应的JSX
对象。
!!!注意这里与Vue相同必须给循环绑定的JSX元素绑定key属性,区分不同的元素,否则直接报错
import React from "react";
import ReactDOM from "react-dom";
const arr = [1231, 233, 15, 123, 15, 15, 1, 1, 56, 12, 156];
const ele = (
<div>
<ul>
{arr.map((item, index) => {
return <li key={index}>{item}</li>;
})}
</ul>
</div>
);
ReactDOM.render(ele, document.querySelector("#root"));
4.2.2循环对象
import React from "react";
import ReactDOM from "react-dom";
const arr = { name: "F_picacho", age: 18 };
const ele = (
<div>
<ul>
{Object.keys(arr).map((item, index) => {
return <li key={index}>{item}</li>;
})}
</ul>
</div>
);
ReactDOM.render(ele, document.querySelector("#root"));
4.2过滤
这里模拟了一组json数据goods大于等于500的商品显示 否则不显示
import React from "react";
import ReactDOM from "react-dom";
const goods = [
{ id: 1, price: 100, title: "小米1" },
{ id: 2, price: 200, title: "小米2" },
{ id: 3, price: 500, title: "小米3" },
{ id: 4, price: 1000, title: "小米4" },
{ id: 5, price: 3000, title: "小米5" }
];
const ele = (
<div>
<ul>
{goods.map(good => {
return good.price >= 500 ? <li key={good.id}>{good.title}</li> : null;
})}
</ul>
</div>
);
ReactDOM.render(ele, document.querySelector("#root"));
5 React中创建组件
随着项目越做越大需要的JSX对象也越来越多所以的JSX对象堆在index.js会造成代码冗余,导致后期维护困难。
React核心的思想就是组件化开发我们可以利用组件降低代码冗余提高复用效率。
5.1函数声明组件
import React from "react";
import ReactDOM from "react-dom";
//函数声明
function Welcome() {
return <h2>Hello React</h2>;
}
const ele = (
<div>
<Welcome /> //引用直接使用函数名标签引用 可以写单闭合标签也可以写双闭合标签
</div>
);
ReactDOM.render(ele, document.querySelector("#root"));
!!!注意组件名无论使用哪一种声明方式一定要大写否则报错,函数式声明的组件必须返回一个JSX对象
5.1.1 函数声明组件传值
组件通常都跟他的属性是有关联的 我们可以在组件引用的位置传入一些自定义属性 并且使用props接收
import React from "react";
import ReactDOM from "react-dom";
//函数声明
function Welcome(props) {
return <h2>Hello {props.name}</h2>;
}
const ele = (
<div>
<Welcome name="F_picacho" />
</div>
);
ReactDOM.render(ele, document.querySelector("#root"));
5.2类声明组件
import React from "react";
import ReactDOM from "react-dom";
class Welcome extends React.Component {
render() {
return <h1>{this.props.name}</h1>;
}
}
const ele = (
<div>
<Welcome name="F_picacho" />
</div>
);
ReactDOM.render(ele, document.querySelector("#root"));
新建一个类继承于 React.Component
且必须有render()
函数将虚拟DOM转换为真实DOM并且return
一个JSX
元素
5.2.1 类声明组件传值
使用this.props.在标签定义的自定义属性
如果类中有constructor需要将props继承下来即:
class Welcome extends React.Component {
constructor(props) {
super(props);
}
render() {
return <h1>{this.props.name}</h1>;
}
}
5.2.2 类声明组件模块化引入
将上面的代码剪切并且在src下建立一个新的文件Header.js
src->Header.js
import React from "react";
class Welcome extends React.Component {
constructor(props) {
super(props);
}
render() {
return <h1>{this.props.name}</h1>;
}
}
export default Welcome; //抛出Welcome
在index.js中引入
src->index.js
import React from "react";
import ReactDOM from "react-dom";
import Header from "./Header"; //引入模块
const ele = (
<div>
<Header name="F_picacho" />
</div>
);
ReactDOM.render(ele, document.querySelector("#root"));
可以通过rcc指令快速生成类声明组件模板 webstrom自带 vscode需要安装拓展插件
6 复用组件
在同一个组件中可以抽离出任意层次的小结构,这种小的结构我们称之为复用组件。例如网页中的按钮,表单,对话框,甚至整个页面的内容。
- 将多个组件进行整合例如调用两次以上的组件
- 结构非常复杂时需要将组件拆分成小组件
- 会存在父子关系的数据传递
现在比如说我有一个这样的组件,我们可以发现这三个button组件是重复的不同的就是内容或者是里面的逻辑,这种的组件我们就可以把他们拆分出来。
//拆分前
class Welcome extends React.Component {
constructor(props) {
super(props);
}
render() {
return (
<div>
<button>提交</button>
<button>删除</button>
<button>清空</button>
</div>
);
}
}
//拆分后
import React, { Component } from "react"; //这里将Component解构了出来方便后续使用
class Mybutton extends Component {
render() {
return (
<div>
<button>{this.props.title}</button>
</div>
);
}
}
class Welcome extends React.Component {
render() {
return (
<div>
<Mybutton title="提交" />
<Mybutton title="删除" />
<Mybutton title="清空" />
</div>
);
}
}
6.1 如何在React项目中提取组件
比如说我这里有一个这样的表单组件
import React, { Component } from "react";
class Welcome extends React.Component {
constructor(props) {
super(props);
//这里依然被模拟的是从后端来的数据
this.user = {
name: "F_picacho",
content: "这是我的React组件",
date: new Date().toLocaleTimeString()
};
}
render() {
return (
<div>
<div className="comment">
{/*用户信息*/}
<div className="userinfo">
<p>用户信息:{this.user.name}</p>
</div>
{/*评论内容*/}
<div className="comment-container">
<p>评论内容:{this.user.content}</p>
</div>
{/*评论时间*/}
<div className="comment-time">
<p>发布时间:{this.user.date}</p>
</div>
</div>
</div>
);
}
}
export default Welcome;
这样的组件乍看下没有什么问题,但是我们这个例子是模拟的真是开发项目当中这个组件肯定是很复杂的,那这个时候我们就需要进行拆分以便于我们后期维护。
我们可以这样拆分上面的组件 首先肯定是要把comment
抽离出来然后继续拆分把userinfo
comment-container
comment-time
抽离拆分,例如这样:
import React, { Component } from "react";
class Comment extends Component {
render() {
return (
<div className="comment">
{/*用户信息*/}
<div className="userinfo">
<p>用户信息:{this.props.user.name}</p>
</div>
{/*评论内容*/}
<div className="comment-container">
<p>评论内容:{this.props.user.content}</p>
</div>
{/*评论时间*/}
<div className="comment-time">
<p>发布时间:{this.props.user.date}</p>
</div>
</div>
);
}
}
class Welcome extends React.Component {
constructor(props) {
super(props);
this.user = {
name: "F_picacho",
content: "这是我的React组件",
date: new Date().toLocaleTimeString()
};
}
render() {
return (
<div>
<Comment user={this.user} />
</div>
);
}
}
export default Welcome;
这样就把Comment
组件抽离出来了,当然你需要注意一下数据流向以及如何获取数据我们还可以把用户信息,评论内容,时间继续往外抽离例如这样:
import React, { Component } from "react";
class Userinfo extends Component {
render() {
return (
<div className="userinfo">
<p>用户信息:{this.props.user.name}</p>
</div>
);
}
}
class Commentcontainer extends Component {
render() {
return (
<div className="comment-container">
<p>评论内容:{this.props.user.content}</p>
</div>
);
}
}
class Commenttime extends Component {
render() {
return (
<div className="comment-time">
<p>发布时间:{this.props.user.date}</p>
</div>
);
}
}
class Comment extends Component {
render() {
return (
<div className="comment">
{/*用户信息*/}
<Userinfo user={this.props.user} />
{/*评论内容*/}
<Commentcontainer user={this.props.user} />
{/*评论时间*/}
<Commenttime user={this.props.user} />
</div>
);
}
}
class Welcome extends React.Component {
constructor(props) {
super(props);
this.user = {
name: "F_picacho",
content: "这是我的React组件",
date: new Date().toLocaleTimeString()
};
}
render() {
return (
<div>
<Comment user={this.user} />
</div>
);
}
}
export default Welcome;
这样我们就把一个表单组件拆分成很多个细小的组件,后期维护也很容易,哪里需要修改就重写哪里的代码就可以了
7 组件引入样式
呐前面说了这么多一直没用说过关于React如何组织样式的问题,这里就交代一下,其实就是写一个CSS然后将其使用模块化引入这也体现了React一切皆模块的思想。
在外部新建一个css文件在组件中使用import
引入即可import "./demo.css";
8 父子组件通信
在开发项目的过程中我们经常需要进行组件传值例如一个购物车的页面,显示购买数量是一个组件,加入购物车是一个组件,我在点击加入购物车的组件显示购买数量的组件内容就+1这里就涉及到了组件通信。组件通信又分为几种情况例如父组件->子组件
,子组件->父组件
,平行组件(兄弟组件)
,跨组件
这四种。
8.1 父组件->子组件
其实父往子组件传值我们上面的demo写过很多次了父组件中的数据利用自定义属性进行传递,一层一层的传递到子组件中,类声明子组件使用this.props.自定义属性名
接收,例如:
class Comment extends Component {
render() {
return (
<div className="comment">
{/*用户信息*/}
<Userinfo user={this.props.user} />
{/*评论内容*/}
<Commentcontainer user={this.props.user} />
{/*评论时间*/}
<Commenttime user={this.props.user} />
</div>
);
}
}
class Welcome extends React.Component {
constructor(props) {
super(props);
this.user = {
name: "F_picacho",
content: "这是我的React组件",
date: new Date().toLocaleTimeString()
};
}
render() {
return (
<div>
<Comment user={this.user} />
</div>
);
}
}
注意父组件往子组件只能一层层的传递,因为React遵循的是单项数据流。可能你会觉得组件一但嵌套的层数多了之后这样通信显得很麻烦,在后面的内容会介绍如何解决这一问题。
<div>
<Comment user={this.user} />
</div>
);
}
}
8.2 子组件->父组件
子往父传值的思路是子组件通过this.props触发一个从父组件中定制的方法进行传值例如:
import React, { Component } from "react";
class Comment extends Component {
// 4.这时我们就可以通过this.props拿到父组件中的add事件并且在子组件中触发
CommentClick() {
this.props.add();
}
render() {
// 3.子组件中使用原生js的方式绑定事件
return (
<div className="comment">
<button onClick={this.CommentClick}></button>
</div>
);
}
}
class Welcome extends React.Component {
// 1.父组件中定义事件
add() {
alert("父组件的事件");
}
render() {
// 2.自定义属性将父组件中的事件绑定传递出去
return (
<div>
<Comment add={this.add} />
</div>
);
}
}
export default Welcome;
这代码乍看没用问题,但实际上我们测试的过程中点击按钮后会抛出一个错误即:
!!!类型错误props
为undefined
,发生这种错误我们就该意识到了这个this到底指向了哪里?呐js基础扎实的话就应该意识到这里的一个坑CommentClick
里的this
其实是指向了事件触发的对象而不是Comment组件
所以错误就产生了。
所以说我们要改变当前this
的指向,那这里直接介绍一种最直接的方式就是使用ES6中的箭头函数,箭头函数会根据作用域链往上查找最顶层的对象所以我们把事件改造成一个箭头函数就可以了。
CommentClick = () => {
this.props.add();
};
不管是在Vue中还是React中使用箭头函数都可以避免this指向错误的问题。
呐,进行到了这里我们已经实现了父组件中定义的事件被子组件调用,下一步就是子组件提供出来的值传递到父组件中。
import React, { Component } from "react";
class Comment extends Component {
CommentClick = () => {
this.props.add("子组件中的数据");
};
render() {
return (
<div className="comment">
<button onClick={this.CommentClick}>点我</button>
</div>
);
}
}
class Welcome extends React.Component {
// 父组件接收从子组件传递过来的数据
add(val) {
alert(val);
}
render() {
return (
<div>
<Comment user={this.user} add={this.add} />
</div>
);
}
}
export default Welcome;
9 React中的状态state
每一个组件中都会维护他自己的一个状态,什么是状态?说白了就是组件中维护的数据。
状态一般都会声明在constructor
构造函数当中,假设我定制一个状态count
为0即:
class Welcome extends Component {
constructor(props) {
super(props);
this.state = {
count: 0
};
}
render() {
return (
<div>
<h1>{this.state.count}</h1>
</div>
);
}
}
export default Welcome;
这样我们的count状态就会显示到h1标签中
呐假设我当前组件有一个按钮,单击后count值每次+1可以这么实现:
!!!注意只有类声明的组件才拥有state
函数式声明的没有,在constructor
中可以直接修改state
在其他地方修改状态必须使用setState
。
import React, { Component } from "react";
class Welcome extends Component {
constructor(props) {
super(props);
this.state = {
count: 0
};
}
add() {
this.setState({
count: this.state.count+=1
});
}
render() {
return (
<div>
<h1>{this.state.count}</h1>
<button onClick={this.add}>+1</button>
</div>
);
}
}
export default Welcome;
随后错误就来了
又是一个this指向问题,这个是毫无疑问的因为当前add方法里的this是指向调用它的对象而非Welcome本身,解决这类问题我们有以下几种手段
- 在constructor使用this绑定
this.add = this.add.bind(this)
-
<button onClick={this.add.bind(this)}>+1</button>
-
把add()改造成箭头函数 add=()=>{} !!!推荐
-
<button onClick={()=>this.add}>+1</button>
9.1 setState使用
组件中状态的来源有两种一种是外界的状态我们可以通过
props
接收,另外一种就是组件自身的状态state
,如果我想修改state
的状态就必须使用setState
。
setState是一个异步操作,在数据发生改变后你立即调用他你得到的结果可能不是你先要的结果,在React当中我们可以这样解决:
add = () => {
this.setState(
(prevState, prevProps) => ({
count: prevState.count + 1
}),
() => {
console.log(this.state.count);
}
);
};
setState这个方法第一个参数是一个箭头函数得返回一个对象,函数内接收两个参数prevState
(之前的状态)和prevProps
(之前的属性)。第二个也是一个回调函数最新的状态可以在第二个回调函数中获取到。
所以用函数式进行修改永远都不会出错,对象形式修改可能会产生异步问题,获取不到正确的状态。
10 React生命周期
如图,React生命周期主要包括三个阶段:初始化阶段、运行中和销毁阶段,在Reaact不同的生命周期里,会依次触发不同的钩子函数。
具体每一个钩子函数触发后会发生什么,什么阶段适合触发什么样的方法以后会跟进。
11 受控组件与非受控组件
11.1受控组件
受状态控制的组件叫受控组件,需要与状态进行相应的绑定。
class Controlnput extends Component {
constructor(props) {
super(props);
this.state = {
val: ''
};
}
handleChange = (e)=>{
let val = e.target.value;
this.steState({
val
})
}
render() {
return (
<div>
<input type="text" value={this.state.val} onChange={this.handleChange}/>
</div>
);
}
}
- 受控组件必须要有一个onChange事件
- 受控组件可以赋予默认值
- 可以使用受控组件实现双向绑定
11.2 非受控组件
class NoControlnput extends Component {
constructor(props) {
super(props);
this.state = {
val: ''
};
}
handleChange = ()=>{
let val = this.refs.a.value;
this.steState({
val
})
}
render() {
return (
<div>
<input type="text" onChange={this.handleChange} ref="a"/>
<h2> {this.state.val} </h2>
</div>
);
}
}
12 表单的使用
在开发的过程中经常遇到需要表单的需求,在这里单独抽离出来一个单元和大家分享下表单的基本使用方法。
一个简单的用户登录Demo
import React, { Component } from "react";
class FormSimple extends Component {
constructor(props) {
super(props);
this.state = {
username: "",
password: ""
};
}
handleUsername = e => {
this.setState({
username: e.target.value
});
};
handlePassword = e => {
this.setState({
password: e.target.value
});
};
handelSubmit = e => {
e.preventDefault();
if (this.state.username && this.state.password) {
// 发起ajax请求;
alert(`当前用户名:${this.state.username}当前密码:${this.state.password}`);
}
};
render() {
return (
<div>
<form onSubmit={this.handelSubmit}>
<p className="username">
<label htmlFor="name">用户名:</label>
<input
type="text"
value={this.state.username}
onChange={this.handleUsername}
id="name"
/>
</p>
<p className="password">
<label htmlFor="password">密码:</label>
<input
type="password"
value={this.state.password}
onChange={this.handlePassword}
id="pwd"
/>
</p>
<input type="submit" value="登录" />
</form>
</div>
);
}
}
export default FormSimple;
这里的代码就不多解释了,唯一需要注意的是记得清除form表单的默认事件,使用javascript:;
或者使用preventDefault()
值得注意的是这是一个受控组件。
这里再多演示一个标签
import React, { Component } from "react";
class FormSimple extends Component {
constructor(props) {
super(props);
this.state = {
username: "",
password: "",
selectedArr: []
};
}
handleUsername = e => {
this.setState({
username: e.target.value
});
};
handlePassword = e => {
this.setState({
password: e.target.value
});
};
handelSubmit = e => {
e.preventDefault();
if (this.state.username && this.state.password) {
// 发起ajax请求;
let arr = this.state.selectedArr;
alert(
`当前用户名:${this.state.username}当前密码:${this.state.password},${arr}`
);
}
};
handleChange = e => {
let newArr = [...this.state.selectedArr];
newArr.push(e.target.value);
this.setState({
selectedArr: newArr
});
};
render() {
return (
<div>
<form onSubmit={this.handelSubmit}>
<p className="username">
<label htmlFor="name">用户名:</label>
<input
type="text"
value={this.state.username}
onChange={this.handleUsername}
id="name"
/>
</p>
<p className="password">
<label htmlFor="password">密码:</label>
<input
type="password"
value={this.state.password}
onChange={this.handlePassword}
id="pwd"
/>
</p>
<input type="submit" value="登录" />
<p></p>
我的爱好:
<select
multiple
value={this.state.selectedArr}
onChange={this.handleChange}
>
<option value="smoking">抽烟</option>
<option value="drink">喝酒</option>
<option value="tangto">烫头</option>
</select>
</form>
</div>
);
}
}
export default FormSimple;
结束
呐,在这里React基础部分就已经写的差不多了往后还会继续跟进React的进阶内容,例如组件化啦,全家桶啦,常用的第三方库之类的,在这一节就算告一段落了往后随着我的不断学习也会在Blog里更新更多的内容。
首发于:F_picachoの领域
同步发布于:掘金
作者:F_picacho
技术指导:我的两位师傅 @刘建伟 @Mjj
感谢:@白菜 对Blog的运营以及维护
文档下载:https://ww.lanzous.com/icp1nlc
2020/05/16