1.前言
react视频和结合学习总结摘抄。
1.准备起航
创建项目:npx create-react-app my-app
打开项目:cd my-app
启动项目:npm start
暴露配置项:npm run eject
文件结构:
├── README.md ⽂档
├── public 静态资源
│ ├── favicon.ico
│ ├── index.html
│ └── manifest.json
└── src 源码
├── App.css
├── App.js 根组件
├── App.test.js
├── index.css 全局样式
├── index.js ⼊⼝⽂件
├── logo.svg
└── serviceWorker.js pwa⽀持
├── package.json npm 依赖
入口文件:webpack.config.js
// Check if TypeScript is setup
const useTypeScript = fs.existsSync(paths.appTsConfig);
// style files regexes
const cssRegex = /\.css$/;
const cssModuleRegex = /\.module\.css$/;
const sassRegex = /\.(scss|sass)$/;
const sassModuleRegex = /\.module\.(scss|sass)$/;
entry: [
// WebpackDevServer客户端,它实现开发时热更新功能
isEnvDevelopment &&
require.resolve('react-dev-utils/webpackHotDevClient'),
// 应⽤程序⼊⼝:src/index
paths.appIndexJs,
].filter(Boolean),
2.JSX
jsx是一个 JavaScript 的语法扩展,类似模板语言。JSX 是js对象,也是一个表达式。
const name = 'world'
const element = <h1>Hello, {name}!</h1>
// 函数
function formatName(user) {
return user.firstName + ' ' + user.lastName
}
const user = {
firstName: 'Harper',
lastName: 'Perez'
};
const element = (
<h1>
Hello, {formatName(user)}!
</h1>
);
ReactDOM.render(
element,
document.getElementById('root')
);
function getGreeting(user) {
if (user) {
return <h1>Hello, {formatName(user)}!</h1>;
}
return <h1>Hello, Stranger.</h1>;
}
const greet = <div>good</div>;
const jsx = <div>{greet}</div>;
// 因为 JSX 语法上更接近 JavaScript 而不是 HTML,所以 React DOM 使用 camelCase(小驼峰命名)来定义属性的名称,而不使用 HTML 属性名称的命名约定。
const element = <div tabIndex="0"></div>;
const element = <img src={user.avatarUrl}></img>;
// 条件语句
const show = true;
const greet = <div>good</div>;
const jsx = (
<div>
{show ? greet : "登录"}
{show && greet}
</div>
);
// 表示对象
1.方法一:
const element = (
<h1 className="greeting">
Hello, world!
</h1>
);
const element = React.createElement(
'h1',
{className: 'greeting'},
'Hello, world!'
);
2.方法二:
const element = {
type: 'h1',
props: {
className: 'greeting',
children: 'Hello, world!'
}
};
// 数组
const a = [0, 1, 2];
const jsx = (
<div>
<ul>
{/* diff时候,⾸先⽐较type,然后是key,所以同级同类型元素,key值必须得 唯⼀ */}
{a.map(item => (
<li key={item}>{item}</li>
))}
</ul>
</div>
);
// 属性
import logo from "./logo.svg";
const jsx = (
<div>
{/* 属性:静态值⽤双引号,动态值⽤花括号;class、for等要特殊处理。 */}
<img src={logo} style={{ width: 100 }} className="img"/>
</div>
);
// css模块化
import style from "./base.css";
<img className={style.logo} />
3.组件(组件名采用全驼峰式,比如Clock,ClockName,第一个单词大写)
1.class组件
class组件通常拥有状态和⽣命周期,继承于Component,实现render⽅法。
import ClassComponent from './ClassComponent'; // 引入路径(路径按自己定义的位置)
<ClassComponent /> // 使用
import React, { Component } from 'react'
export default class Clock extends Component {
constructor(props) {
super(props);
this.state = {
date: new Date()
}
}
componentDidMount() { // 组件挂载完成后执行
this.timer = setInterval(() => {
this.setState({
date: new Date()
});
}, 1000);
}
componentWillUnmount() { // 组件卸载之前执行
clearInterval(this.timer)
}
componentDidUpdate() { // 组件更新(一直在执行)
console.log('componentDidUpdate')
}
render() {
const { date } = this.state
return (
<div>{date.toLocaleTimeString()}</div>
)
}
}
2.function组件
函数组件通常⽆状态(hooks就有状态了),仅关注内容展示,返回渲染结果即可。 难点:依赖项理解与使用
import { FunctionComponent } from './FunctionComponent';
<FunctionComponent />
import React, { useState, useEffect } from 'react';
export function FunctionComponent(props) {
const [date, setDate] = useState(new Date());
useEffect(() => { // 相当于componentDidMount和componentWillUnmount集合
console.log('uesEffect')
const timer = setInterval(() => {
setDate(new Date());
}, 1000);
return () => clearInterval(timer) // 组件卸载的时候执行
}, []); // 依赖项(useEffect里面改变了就重新执行,当前不需要改变就[])没有就一直执行
// 依赖项相当于componentDidUpdate
return (
<div>
{date.toLocaleTimeString()}
</div>
)
}
4.正确地使用setState
setState(partialState, callback)
1. partialState: object|function
⽤于产⽣与当前state合并的⼦集。
2. callback: function
state更新之后被调⽤。
1.不要直接修改state
// 错误
this.state.comment = 'hello'
// 正确
this.setState({comment: 'hello'})
构造函数是唯一可以给 this.state 赋值的地方
2.state 的更新可能是异步的
出于性能考虑,React 可能会把多个 setState() 调用合并成一个调用。
因为 this.props 和 this.state 可能会异步更新,所以你不要依赖他们的值来更新下一个状态。
// 错误
this.setState({
counter: this.state.counter + this.props.increment
});
// 正确
this.setState((state, props) => ({
counter: state.counter + props.increment
}));
// 例子(获取的是旧值)
import React, { Component } from "react";
export default class SetStatePage extends Component {
constructor(props) {
super(props);
this.state = {
counter: 0
};
}
changeValue = v => {
this.setState({
counter: this.state.counter + v
});
console.log("counter", this.state.counter);
};
setCounter = () => {
this.changeValue(1);
// console.log("counter", this.state.counter);
};
render() {
const { counter } = this.state;
return (
<div>
<h3>SetStatePage</h3>
<button onClick={this.setCounter}>{counter}</button>
</div>
);
}
// 如果要获取到最新状态值有以下⽅式
// 1. 在回调中获取状态值
changeValue = v => {
this.setState({
counter: this.state.counter + v
}, () => {
// 这里的callback函数就是state更新完成之后在执行的
console.log("counter", this.state.counter);
});
};
// 2.使⽤定时器
setTimeout(() => {
this.setCounter();
}, 0);
// 3.原⽣事件中修改状态
componentDidMount() { // 挂载
document.body.addEventListener('click', this.changeValue, false)
}
// 总结:setState只有在合成事件和⽣命周期函数中是异步的,在原⽣事件和setTimeout中都是同步
的,这⾥的异步其实是批量更新(所以导致合并)。上面是同步操作,这里同步指的是当前执行setState完后立马获取最新的值。
3.state的更新会被吞并
constructor(props) {
super(props);
this.state = {
posts: [],
comments: []
};
}
componentDidMount() {
fetchPosts().then(res => {
this.setState({
posts: res.posts
});
});
fetchComments().then(res => {
this.setState({
comments: res.comments
});
});
}
changeValue = v => {
this.setState({
counter: this.state.counter + v
});
};
setCounter = () => {
this.changeValue(1);
this.changeValue(2); // 批量更新,导致后面的会覆盖前面的
};
// 解决就是链式(函数)更新state:
changeValue = v => {
this.setState(state => ({ counter: state.counter + v }));
};
setCounter = () => {
this.changeValue(1);
this.changeValue(2);
};
5.组件复合与继承(children)
1.不具名
import React, { Component } from 'react';
import Layout from './Layout';
export default class HomePage extends Component {
render() {
return (
<Layout title="首页">
<div>
home page
</div>
</Layout>
);
}
}
import React, { Component } from 'react';
export default class Layout extends Component {
componentDidMount() {
const { title } = this.props
document.title = title
}
render() {
const { children } = this.props
console.log(children) // {$$typeof: Symbol(react.element), type: "div"......
return (
<div>
{this.props.children}
</div>
)
}
}
2.具名(传对象进去)
import React, { Component } from 'react';
import Layout from './Layout';
export default class HomePage extends Component {
render() {
return (
<Layout title="首页">
{ // 传对象{}
{ // 语法
content: (
<div>
<h3>home page</h3>
</div>
),
txt: '文本',
btnClick: () => {
console.log('button')
}
}
}
</Layout>
);
}
}
import React, { Component } from 'react';
export default class Layout extends Component {
componentDidMount() {
const { title } = this.props
document.title = title
}
render() {
const { children } = this.props
console.log(children) // content: {$$typeof: Symbol(react.element), type: "div", key: null, ref: null, props: {…}, …}......
return (
<div>
{children.content}
{children.txt}
<button onClick={children.btnClick}>btn</button>
</div>
)
}
}
6.生命周期
⽣命周期⽅法,⽤于在组件不同阶段执⾏⾃定义功能。在组件被创建并插⼊到 DOM 时(即挂载中阶段),组件更新时,组件取消挂载或从 DOM 中删除时,都有可以使⽤的⽣命周期⽅法。 作者:爱吃芋圆的小w 生命周期:
React V16.3之前的⽣命周期:
componentDidMount // 组件第一次渲染完成,此时dom节点已经生成,可以在这里调用ajax请求,返回数据setState后组件会重新渲染。
componentWillUnmount // 在此处完成组件的卸载和数据的销毁。(定时器销毁,监听事件移除)
componentWillReceiveProps(nextProps) // 在接受父组件改变后的props需要重新渲染组件时使用,初次渲染不执行
shouldComponentUpdate(nextProps, nextState) // 主要用于性能优化(部分更新,不重新渲染)。因为react父组件的重新渲染会导致其所有子组件的重新渲染,这个时候其实我们是不需要所有子组件都跟着重新渲染的,因此需要在子组件的该生命周期中做判断。唯一用于控制组件重新渲染的生命周期,由于在react中,setState以后,state发生变化,组件会进入重新渲染的流程,在这里return false可以阻止组件的更新,意思是不会再执行componentWillUpdate和componentDidUpdate生命周期了。
render() // render函数会插入jsx生成的dom结构,react会生成一份虚拟dom树,在每一次组件更新时,在此react会通过其diff算法比较更新前后的新旧DOM树,比较以后,找到最小的有差异的DOM节点,并重新渲染。
V17可能会废弃的三个⽣命周期函数⽤getDerivedStateFromProps替代,⽬前使⽤的话加上
UNSAFE_:
componentWillMount
componentWillReceiveProps
componentWillUpdate
引⼊两个新的⽣命周期函数:
static getDerivedStateFromProps
getSnapshotBeforeUpdate
如果不想⼿动给将要废弃的⽣命周期添加 UNSAFE_ 前缀,可以⽤下⾯的命令。
npx react-codemod rename-unsafe-lifecycles <path>
新引⼊的两个⽣命周期函数
static getDerivedStateFromProps(props, state)
getSnapshotBeforeUpdate(prevProps, prevState)
getDerivedStateFromProps 会在调⽤ render ⽅法之前调⽤,并且在初始挂载及后续更新时都会被
调⽤。它应返回⼀个对象来更新 state,如果返回 null 则不更新任何内容。
getSnapshotBeforeUpdate() 在最近⼀次渲染输出(提交到 DOM 节点)之前调⽤。它使得组件能
在发⽣更改之前从 DOM 中捕获⼀些信息(例如,滚动位置)。此⽣命周期的任何返回值将作为参数(snapshot)传
递给 componentDidUpdate(prevProps, prevState, snapshot) 。
7.redux(类似vuex)
redux文档还有免费视频学习
有着相当⼤量的、随时间变化的数据;你的 state 需要有⼀个单⼀可靠数据来源;你觉得把所有 state 放在最顶层组件中已经⽆法满⾜需要了;某个组件的状态需要共享。 安装:npm install redux --save
1. createStore 创建store
import {createStore} from 'redux'
const func = (state = 0, action) => state
const store = createStore(func)
2. reducer 初始化、修改状态函数
function
3. getState 获取状态值
store.getState()
4. dispatch 提交更新
store.dispatch({})
5. subscribe 变更订阅(state变化更新,重新渲染)
store.subscribe(() => {
console.log("subscribe");
this.forceUpdate(); // 强制更新视图渲染(类似vue的this.$forceUpdate())
// this.setState({});
});
8.react-redux
安装:npm install react-redux --save
react-redux提供了两个api
1. Provider 为后代组件提供store
2. connect 为组件提供数据和变更⽅法(connect中的参数:state映射和事件映射)
import React from 'react'
import ReactDom from 'react-dom'
import App from './App'
import store from './store/'
import { Provider } from 'react-redux'
ReactDom.render(
<Provider store={store}>
<App/>
</Provider>,
document.querySelector('#root')
)
import React, { Component } from "react";
import { connect } from "react-redux";
class ReactReduxPage extends Component {
render() {
const { num, add, minus, dispatch } = this.props;
return (
<div>
<h1>ReactReduxPage</h1>
<p>{num}</p>
<button onClick={() => dispatch({type: 'ADD'})}>add</button>
<button onClick={add}>add</button>
<button onClick={minus}>minus</button>
</div>
);
}
}
export default connect(
// 状态映射 mapStateToProps
state => ({num: state}),
{ // 派发事件映射
add: () => ({type: "add"}),
minus: () => ({type: "minus"})
}
)(ReactReduxPage);
9.react-router
安装:npm install react-router-dom --save
路由器-Router、链接-Link、路由-Route、独占-Switch、重
定向-Redirect都以组件形式存在。
Route渲染内容的三种⽅式
Route渲染优先级:children>component>render。
这三种⽅式互斥,你只能⽤⼀种。
children:func
有时候,不管location是否匹配,你都需要渲染⼀些内容,这时候你可以⽤children。
除了不管location是否匹配都会被渲染之外,其它⼯作⽅法与render完全⼀样。
render:func
但是当你⽤render的时候,你调⽤的只是个函数。
只在当location匹配的时候渲染。
component: component
只在当location匹配的时候渲染。
import React, { Component } from "react";
import { BrowserRouter as Router, Route, Link } from "react-router-dom";
export default class RouterPage extends Component {
render() {
return (
<div>
<h3>RouterPage</h3>
<Router>
<Link to="/">⾸⻚</Link>
<Link to="/user">⽤户中⼼</Link>
{/* 根路由要添加exact,实现精确匹配(只匹配一个) */}
<Route
exact
path="/"
component={HomePage}
//children={() => <div>children</div>}
//render={() => <div>render</div>}
/>
<Route path="/user" component={UserPage} /></Router>
</div>
);
}
}
class HomePage extends Component {
render() {
return (
<div>
<h3>HomePage</h3>
</div>
);
}
}
class UserPage extends Component {
render() {
return (
<div>
<h3>UserPage</h3>
</div>
);
}
}
404页面(设定⼀个没有path的路由在路由列表最后⾯,表示⼀定匹配)
{/* 添加Switch表示仅匹配⼀个*/}
<Switch>
{/* 根路由要添加exact,实现精确匹配 */}
<Route
exact
path="/"
component={HomePage} />
<Route path="/user" component={UserPage} />
<Route component={EmptyPage} />
</Switch>
class EmptyPage extends Component {
render() {
return (
<div>
<h3>404</h3>
</div>
);
}
}
1.PureComponent浅比较(只比较一个state,多个在一起无效)
import React, { Component, PureComponent } from "react";
export default class PureComponentPage extends PureComponent {
constructor(props) {
super(props);
this.state = {
counter: 0
};
}
setCounter = () => {
this.setState({
counter: 100
});
};
// PureComponent内置值不变化不再渲染
// shouldComponentUpdate(nextProps, nextState) { // 当值没变化时不执行渲染
// return nextState.count !== this.state.count;
// }
render() {
const { counter, obj } = this.state;
return (
<div>
<h1>PuerComponentPage</h1>
<div onClick={this.setCounter}>counter: {counter}</div>
</div>
);
}
}