React环境搭建
环境安装
-
安装nodejs
-
安装react脚手架
npm install create-react-app -g
创建项目
npx create-react-app my-app
cd my-app
npm start
项目结构
react的加载流程
React的入口在index.js
index.js中会使用App.js作为组件加载
加载完成再渲染到HTML中
index.js
import React from 'react';
import ReactDOM from 'react-dom';
import './index.css'; /* 引入当前节点需要用到的css文件 */
import App from './App'; /* 引入需要用到的React组件 */
import reportWebVitals from './reportWebVitals';
ReactDOM.render(
<React.StrictMode>
<App />
</React.StrictMode>, /* ReactDOM根节点的content,使用的使JSX语法 */
document.getElementById('root') /* 指定ReactDOM的绑定对象 */
);
// If you want to start measuring performance in your app, pass a function
// to log results (for example: reportWebVitals(console.log))
// or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals
reportWebVitals();
App.js
import logo from './logo.svg'; /* 引入图片路径 */
import './App.css'; /* 引入css文件 */
function App() {
//使用 JSX语法描述组件的UI结构
return (
<div className="App">
<header className="App-header">
<img src={logo} className="App-logo" alt="logo" />
<p>
Edit <code>src/App.js</code> and save to reload.
</p>
<a
className="App-link"
href="https://reactjs.org"
target="_blank"
rel="noopener noreferrer"
>
Learn React
</a>
</header>
</div>
);
}
export default App;
React视图层
JSX语法
是基本js的扩展语法
jsx描述UI结构
const element = (
<h1 className="greeting">
Hello world!
</h1>
)
会被在React内部转化成React 元素
//结构有简化
const element = {
type:'h1',
props:{
className:'greeting',
children:'Hello,world!'
}
}
React元素渲染
ReactDOM.render(
<React.StrictMode>
<App />
</React.StrictMode>, /* ReactDOM根节点的content,使用的使JSX语法 */
document.getElementById('root') /* 指定ReactDOM的绑定对象 */
);
组件
React组件是在React元素之上的封装,是React应用 组成 和 复用 的基本单元。
component = (props) => element
组件就像一个函数,接收props,然后返回一个元素
React组件必须像 纯函数一样运行
纯函数
- 对于相同的入参,返回同样的结果
- 无副作用(副作用:当调用函数时,除了返回函数值之外,还对主调用函数产生附加的影响)
为什么说 像 呢?因为在组件运行时,需要根据一些网络请求进行改变。这就会很难避免的产生副作用,我们进行这样操作的时候需要按照React的规范进行。
组件可以分为两类:函数组件和类组件
函数组件
const ArticleList = (props) => {
const { articles } = props;
// 列表渲染,列表中的每一项要设置 key
return articles.map((item) => {
const { id, title } = item;
return <div key={id}> {title}</div>;
});
};
类组件
类组件中必须要重写render方法
class ArticleList extends React.Component {
render() {
const { articles } = this.props;
return articles.map((item) => {
const { id, title } = item;
return <div key={id}> {title}</div>;
});
}
}
调用的时候传入
const articles = [
{ id: 1, title: 'Article 1' },
{ id: 2, title: 'Article 2' },
{ id: 3, title: 'Article 3' },
];
function App() {
return <ArticleList articles={articles} />;
}
对于React来说,在渲染列表的时候需要指定key,如果不指定的话,就会报一个警告的错误
因为React为了性能考虑,会根据key的相同或者不同来判断是否需要元素复用,如果key相同,就会进行元素复用,来提高性能。
state
state的使用
前面我们进行的都是传入的固定的参数,得到的都是静态的元素,但是如果我们希望我们的React元素有一定的可变性,需要怎么做呢?
我们可以通过state
可以让我们的元素具有动态性
需要的注意的是 state
只能在类组件中使用,函数组件中无法使用。
假如我们需要做一个实时显示的时间组件
我们就可以通过下面的例子创建
这里面我们定义了一个state
通过setState
方法来更新里面的数据,这个方法更新完数据之后会重新渲染元素
实现时间的动态显示
import React, { Component } from 'react';
export default class Clock extends 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>Current Time: {this.state.date.toLocaleTimeString()}.</h1>
</div>
);
}
}
组件执行流程
页面显示效果
这里面使用到了setInterval()
其实是访问了外部的api,其实就是我们说的副作用,但我们把副作用代码放在了生命周期函数中调用,只会调用一次,但是render函数中是不能有副作用代码的。
生命周期
由于state
只能再类组件中使用,函数组件中无法使用,所以函数组件是无状态组件,类组件可以是有状态组件。
这里面用到了类组件中的生命周期函数
componentDidMount()
是元素挂载完成后,调用的生命周期函数,只调用一次
componentWillUnmount()
是元素被卸载时,调用的生命周期函数
生命周期方法是类组件中定义的,对于函数组件中没有生命周期的概念
这里面主要看黑色加粗的这几个生命周期,这些是常用的生命周期方法,其他的的不常用,可以先不用关注
setState()
在进行状态更新时 需要使用 setState()
API来进行更改,不能直接的进行访问state
属性做修改
当我们通过setState()
修改状态时,如果新的状态依赖当前的状态,不能给setState()
传入一个对象的参数,而是需要传入一个函数,这个函数会接收两个参数,第一个参数是当前组件的state
,第二个参数是组件最新的props
,这么调用时React内部组件的setState()
触发机制有关
setState()
进行参数更改不是全部覆盖,而是局部更改
Props:父组件传递给子组件的属性,在子组件内部只读
State:组件内部自己维护的状态,内部可以通过setState()
被修改
Form
受控组件
form中的值和值变化的管理逻辑都受React的控制
通过函数进行事件绑定
通过state进行值的更新
非受控组件
这里的input组件是通过ref进行的强绑定
在构造里创建ref
然后再渲染里进行绑定
组件的组合用法
刚刚我们创建的组件内部都是一个完整的整体,但是我们想要做到像普通的标签元素一样,内部可以再添加子元素的话,就涉及到了组件的组合用法。
从这个例子我们可以看出,内部的子元素都会通过props.children
的方式传递到组件中。
这就相当于react在组件上打了一个洞,但有时候一个洞不够我们用怎么办呢?
其实这也很好理解,本身children就相当于React的props
的一个固定属性,它对应着组件的子组件,那如果我们想要传入多个组件,就使用props
的方式传入就行了
Hooks
hooks是React16.8以后提供的一种新的组件开发方式,其实就是一个函数,但这个函数有一个特殊的用途就是用来封装react中函数组件的状态管理逻辑的。
hook只能在函数组件中使用,而不能在类组件中使用
Stake Hook
这是一个最直接最基本的来管理状态的函数
const [state,setState] = useState(initialState);
useState()
接收一个参数,也就是state
这个参数的初始值
返回一个数组类型,数组的第一个元素就是状态,第二个元素是一个方法,这个方法就是对state
进行修改的。
返回值的名字可以随便去取。
function AddPostForm(props){
const [title, setTitle] = useState('');
const [content, setContent] = useState('');
const handleChange = (e) => {
if (e.target.name === 'title') {
setTitle(e.target.value)
} else if (e.target.name === 'content') {
setContent(e.target.value)
}
};
const handleSave = async () => {
if (title.length > 0 && content.length > 0) {
console.log(title, content)
}
};
return (
<section>
<h2>Add a New Post</h2>
<form>
<label htmlFor="title">Post Title:</label>
<input
type="text"
id="title"
name="title"
value={title}
onChange={handleChange}
/>
<label htmlFor="content">Content:</label>
<textarea
id="content"
name="content"
value={content}
onChange={handleChange}
/>
<button type="button" className="button" onClick={handleSave}>
Save Post
</button>
</form>
</section>
);
};
这里面就用到了useState()
const [title, setTitle] = useState('');
const [content, setContent] = useState('');
值得注意的是,我们也可以将单个状态写成对象的形式,但是这样在使用setState()
的时候就会将状态整个替换,不能像类组件中那样局部修改。
const [{title,content},setState] = useState({
title:'',
content:''
})
Effect Hook
我们前面提到类组件的时候,组件的副作用代码会提到组件的生命周期函数中调用。Effect Hook也是可以去解决这个问题的。
useEffect(()=>{effectFn();return clearFn},[...dependencies])
useEffect()
接收两个参数,第一个参数是一个函数,第二个参数是一个数组类型的参数
其中effectFn
就是我们要执行的带有副作用逻辑的函数,这个返回值,这里写的是clearFn
我们可以在clearFn
里面做一些清除副作用的逻辑。每一次useEffect()
执行的时候,都会先执行这个返回函数,clearFn
,在卸载组件的时候也会执行这个函数。
第二个参数是一个可选参数,如果没有指定的话,每次执行useEffect()
的时候,effectFn
都会被执行,但是如果第二参数指定了,假如说指定了依赖[a,b,c]
,那只有a,b,c
这三个变量发生变化的时候,effectFn
才会被执行,如果传入一个空数组,effectFn
只会被执行一次。
useEffect()
每次在组件被渲染的时候都会被执行,但是不代表effectFn
会被执行。
示例
function App() {
const [articles, setArticles] = useState([])
useEffect(()=> {
const fetchArticles = async () => {
try{
const response = await client.get('/fakeApi/posts');
console.log(response)
setArticles(response.posts);
}catch(err){
console.error(err)
}
}
fetchArticles();
}, [])
return (
<div>
<ArticleList articles={articles}/>
</div>
)
}
其他
除了上面两个最常用的,React还提供了其他一些hook的api
具体可以参照Hook API 索引 – React (docschina.org)
自定义hooks
自定义hooks必须要以"use"这个单词作为函数名的开头,封装通用逻辑的函数
import { useEffect } from 'react';
const useMounted = (fn:()=>void) => {
useEffect(()=>{
fn();
},[]);
};
这就相当于自己封装了一个只在加载时执行一次的函数;
import React, { useEffect,useState } from 'react';
function useCurrentDate(){
const [date, setCurrentDate] = useState(new Date());
const tick = () => {
setCurrentDate(new Date())
}
useEffect(() => {
const timer = setInterval(()=>{tick()}, 1000)
return () => {
clearInterval(timer)
}
}, [])
return date;
}
export default function Clock() {
const date = useCurrentDate();
return (
<div>
<h1>Current Time: {date.toLocaleTimeString()}.</h1>
</div>
);
}
这个例子中创建了一个自定义的hook,将date
的改变独立封装成做一个可复用的hook。
规则
使用hook的规则:
- 我们只能在函数组件、自定义hook中调用hook
- 只能在函数的最顶层控制流中调用hook
这两个示例都不算是最顶层控制流
Why Hook?
- 使用Hook可以让我们对组件状态管理逻辑进行复用
- 面向React的未来
在hook之前,我们在开发React应用的时候,大量的使用类组件,但是类组件有一些自身的限制,比如说生命周期方法,this的问题导致类组件的复杂度远远高于函数组件,这也给React做一些性能优化,做一些更高级的特性带来了很大的阻碍,所以React推出了Hook这样的功能,让React中的组件变得更加轻量。