前言:篇幅较长,需要耗费一些时间和精力哈👻
起步
npx create-react-app my-app // 1.创建项目
cd my-app // 2.打开项目
npm start // 3.启动项目
npm run eject // 4.暴露配置项
目录
├── 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
React和ReactDom
import React from 'react'
import ReactDom from 'react-dom'
// JSX => React.createElement(...)
ReactDom.render(<h2>Hello React</h2>,document.querySelector('#root'))
React:负责逻辑控制,数据 -> VDOM
ReactDOM:渲染实际DOM,VDOM -> DOM
React使用JSX描述UI
babel-loader把JSX编译成相应的JS对象,React.createElement再把这个JS对象构造成React需要的虚拟dom
JSX
JSX是JavaScript的一种语法扩展,其格式比较像模板语言,但事实上完全是在JavaScript内部实现的。JSX可以很好的描述UI,能够有效提高开发效率。
- 基本使用
// 表达式{}的使用
const name = 'Cherry'
const jsx = <div>hello,{name}</div>
React.render(jsx,document.querySelector('#root'))
- 函数
// 函数也是合法表达式
const obj = {
firstName: 'Harry',
lastName: 'Potter'
}
function formatName(name) {
return name.firstName + '-' + name.lastName
}
const jsx = <div>hello ,{formatName(obj)}</div>
- 对象
// jsx是js对象,也是合法表达式
const greet = <div>Cherry</div>
const jsx = (
<div>
<div>hello</div>
{greet}
</div>
)
- 条件语句
const show = false;
const greet = <div>Cherry</div>
const jsx = (
<div>
{show ? greet : '登录'}
{show && greet}
</div>
)
- 数组
// 数组会被当作一组元素对待,数组中存放一组jsx可用于显示列表数据
const arr = [1,2,3]
const jsx = (
<ul>
// diff的时候,会先比较type类型,然后是key,所以同级同类型元素,key值必须唯一
{arr.map(item => (<li key={item}>{item}</li>))}
</ul>
)
- 属性
import logo from './logo.png';
const jsx = (
<div>
// 静态值用双引号,动态值用花括号;class、for等要特殊处理
<img src={logo} className="logo" style={{width:100,height:100}} />
</div>
)
- 模块化
// index.js
// 命名空间 css模块化,创建index.module.css
import style from './index.module.css'
const jsx = (
<div className=style.logo>Pink</div>
)
// index.module.css
.logo {
width: 100px;
height: 50px;
color: pink;
}
组件
组件,从概念上类似于JavaScript函数。它接受任意的入参(即“props”),并返回用于描述页面展示内容的React元素。
组件有两种形式:class组件和function组件
- class组件
class组件通常拥有状态和生命周期,继承于Component,实现render方法,下面我们用class组件来写一个小例子
// ClassComponent.js
// 导入React以及成员组件Component
import React, { Component } from 'react'
export default class ClassComponent extends Component {
constructor(props) {
super(props)
this.state = {
date: new Date()
}
}
componentDidMount() {
// 组件挂载之后启动定时器每秒更新状态
this.timer = setInterval(() => {
// 使用setState来更新状态
this.setState({
date: new Date()
})
},1000)
}
componentWillUnmount() {
// 组件卸载前停⽌定时器
clearInterval(this.timer)
}
render() {
return (
<div>{this.state.date.toLocaleTimeString()}</div>
)
}
}
- function组件
函数组件通常无状态,仅关注内容展示,返回渲染结果即可。
从React16.8开始引入了hooks,函数组件也能够拥有状态。
// FunctionComponent.js
import React, {useState, useEffect} from 'react'
export function FunctionComponent(props) {
const [date,setDate] = useState(new Date())
// 相当于 componentDidMount、componentDidUpdate、componentWillUnmount的集合
useEffect(() => { // 副作用
const timer = setInterval(() => {
setDate(new Date())
},1000)
return () => clearInterval(timer) // 组件卸载时执行
},[])
return (
<div>
{date.toLocalTimeString()}
</div>
)
}
setState的使用
setState(partialState, callback)
1.partialState: object|function 用于产生与当前state合并的子集
2.callback : function state更新之后被调⽤。
import的使用(拐个弯🤔)
import语法引用模块时,如何正确使用{}
现在有两个模块分别为A.js,B.js
- import不使用花括号
// A.js
export default 88
// B.js
import A from './A' // 只有在如上A.js中有默认导出的export default语法时才会生效
// 不使用{}来引用模块的情况下,import模块时的命名是随意的,如下:
import A from './A'
import sameA from './A'
import xxx from './A'
// Because 它总是会解析到A.js中默认的export default
- import 使用花括号
// A.js
export const A = 88
// B.js
import { A } from './A' // 只有在如上模块A.js中有命名导出为A的export name的代码
import { A } from './A' // 成功引入,因为A.js中有命名为A的export
import { sameA } from './A' // 引入失败,因为A.js中没有命名为sameA的export
import { xxx } from './A' // 同上
// Because 明确声明了命名导出后,在另一个js中使用{}引入模块时,import时的模块命名是有意义的
// 上述代码正确执行,如下
// A.js
export const A = 88
export const sameA = 99
export const xxx = xxx
👀Warning👀: 一个模块只能有一个默认导出export default,但是可以任意命名导入很多次,也可以一次性都导入,例如:
// B.js
import A, { sameA, xxx } from './A'
// 这里我们使用导入默认导出A,以及命名导出sameA和xxx
// 我们也可以在导入的时候重命名导入
import B, {sameA as sameB, xxx as bbb} from './A'
✍🏿 小结一下:模块的默认导出通常用在导入整个模块的内容,而命名导出用于导入一些有用的公共方法
生命周期
生命周期方法,用于组件不同阶段执行自定义功能。在组件被创建并插入到DOM时(即挂载中阶段mounting),组件更新时,组件取消挂载或从DOM中删除时,都有可以使用的生命周期方法
- React V16.3之前的生命周期
// LifeCyclePage.js
import React, { Component } from "react";
import PropTypes from "prop-types";
export default class LifeCyclePage extends Component {
static defaultProps = {
// msg: "hello",
};
static propTypes = {
// msg: PropTypes.string.isRequired,
};
constructor(props) {
super(props);
this.state = {
count: 0,
};
console.log("constructor");
}
componentWillMount() {
console.log("componentWillMount");
}
componentDidMount() {
console.log("componentDidMount");
}
shouldComponentUpdate(nextProps, nextState) {
const { count } = nextState;
console.log("shouldComponentUpdate", nextState);
// return true 执行更新操作,return false 返回组件运行时
return count !== 3;
}
componentWillUpdate() {
console.log("willComponentUpdate");
}
componentDidUpdate() {
console.log("componentDidUpdate");
}
setCount = () => {
this.setState({ count: this.state.count + 1 });
};
render() {
console.log("render", this.props);
const { count } = this.state;
return (
<div>
<h3>Lifecycle</h3>
<h4>{count}</h4>
<button onClick={this.setCount}>改变count</button>
// {count % 2 && <Child count={count} />}
<Child count={count} />
</div>
);
}
}
class Child extends Component {
componentWillReceiveProps(nextProps) {
console.log("componentWillReceiveProps", nextProps);
}
componentWillUnmount() {
console.log("componentWillUnmount");
}
render() {
console.log("Child render");
const { count } = this.props;
return (
<div>
<h3>Child</h3>
<p>{count}</p>
</div>
);
}
}
- shouldComponentUpdate -> return true
执行顺序: constructor => componentWillMount => render => componentDidMount => shouldComponentUpdate => willComponentUpdate => render => componentDidUpdate => over🤗
👓 componentWillReceiveProps(初次渲染的时候不执行,只有在已挂载的组件接收新的props的时候,才执行)
🕶 componentWillUnmount(组件卸载之前,常用在清除定时器等等...)- shouldComponentUpdate -> return false
执行顺序:constructor => componentWillMount => render => componentDidMount => shouldComponentUpdate => over🙄
- React V16.4之后的生命周期
打开控制台的Warning,如下
Warning.png
V17可能被废弃的三个生命周期函数用getDerivedStateFromProps替代,目前使用的话加上UNSAFE_:
🕸 componentWillMount
🕸 componentWillReceiveProps
🕸 componentWillUpdate
引入两个新的生命周期函数:
🌱 static getDerivedStateFromProps
🌱 getSnapshotBeforeUpdate
如果不想手动给将要废弃的生命周期加上UNSAFE_
前缀,可以用以下命令:
npx react-codemod rename-unsafe-lifecycles <path>
新引入的两个生命周期函数
- getDerivedStateFromProps
static getDerivedStateFromProps(props,state)
这个生命周期函数会在调用render方法之前调用,并且在初始挂载及后续更新时都会被调用。它应返回一个对象来更新state,如果返回null则不更新任何内容。
👀Warning👀:不管原因是什么,都会在每次渲染前触发此方法。这与UNSAFE_componentWillReceiveProps
形成对比,后者仅在父组件重新渲染时触发,而不是在内部调用setState
时。
- getSnapshotBeforeUpdate
getSnapshotBeforeUpdate(prevProps,prevState)
在render之后,在componentDidUpdate之前
这个生命周期函数在最近一次渲染输出(提交到DOM节点)之前调用。它使得组件能在发生更改之前从DOM中捕获一些信息(例如,滚动位置)。此生命周期的任何返回值将作为参数传递给componentDidUpdate(prevProps,prevState,snapshot)
接下来我们看一下执行顺序
import React, { Component } from "react";
import PropTypes from "prop-types";
export default class LifeCyclePage extends Component {
static defaultProps = {
// msg: "hello",
};
static propTypes = {
// msg: PropTypes.string.isRequired,
};
constructor(props) {
super(props);
this.state = {
count: 0,
};
console.log("constructor");
}
// 在render之后,在componentDidUpdate之前
getSnapshotBeforeUpdate(prevProps, prevState, snapshot) {
console.log("getSnapshotBeforeUpdate", prevProps, prevState, snapshot);
// return null;
return {
msg: "hello,我是getSnapshotBeforeUpdate",
};
}
// 在调用render方法之前调用,并且在初始挂载及后续更新时都会被调用
static getDerivedStateFromProps(props, state) {
console.log("getDerivedStateFromProps");
// return null; // 不更新任何内容
const { count } = state;
return count > 5 ? { count: 0 } : null; // 更新state
}
// UNSAFE_componentWillMount() {
// console.log("componentWillMount");
// }
componentDidMount() {
console.log("componentDidMount");
}
shouldComponentUpdate(nextProps, nextState) {
const { count } = nextState;
console.log("shouldComponentUpdate", nextState);
// return false;
return true;
}
// UNSAFE_componentWillUpdate() {
// console.log("willComponentUpdate");
// }
componentDidUpdate(prevProps, prevState, snapshot) {
console.log("componentDidUpdate", prevProps, prevState, snapshot);
}
setCount = () => {
this.setState({ count: this.state.count + 1 });
};
render() {
console.log("render", this.props);
const { count } = this.state;
return (
<div>
<h3>Lifecycle</h3>
<h4>{count}</h4>
<button onClick={this.setCount}>改变count</button>
{/* {count % 2 && <Child count={count} />} */}
<Child count={count} />
</div>
);
}
}
class Child extends Component {
// UNSAFE_componentWillReceiveProps(nextProps) {
// console.log("componentWillReceiveProps", nextProps);
// }
componentWillUnmount() {
console.log("componentWillUnmount");
}
render() {
console.log("Child render");
const { count } = this.props;
return (
<div>
<h3>Child</h3>
<p>{count}</p>
</div>
);
}
}
组件复合
组件复合可以非常敏捷的自定义组件的外观和行为,这种方式更明确和安全。如果组件之间有公用的非UI逻辑,应该将他们抽取为js模块导入使用而不继承。日常开发中我们经常会涉及到组件复用的例子,例如头部和底部复用,下面是一个小的demo🌰
// HomePage.js
import React, { Component } from "react";
import Layout from "./Layout";
export default class HomePage extends Component {
render() {
return (
// showTopBar -> 是否展示顶部栏
// showBottomBar -> 是否展示底部栏
// title -> 标题
<Layout showTopBar={false} showBottomBar={true} title={"我的商城"}>
{/* <div>
<h3>HomePage</h3>
</div> */}
// 具名插槽
{{
content: (
<div>
<h3>HomePage</h3>
</div>
), // 通过jsx写页面内容
txt: "这是一个文本", // 传文本
btnClick: () => console.log("call me"), // 传方法
}}
</Layout>
);
}
}
// Layout.js
import React, { Component } from "react";
import TopBar from "../components/TopBar"; // 顶部栏组件
import BottomBar from "../components/BottomBar"; // 底部栏组件
export default class Layout extends Component {
componentDidMount() {
const { title = "商城" } = this.props;
document.title = title;
}
render() {
const { children, showTopBar, showBottomBar } = this.props;
console.log("children", children);
return (
<div>
{showTopBar && <TopBar />}
<h3>{children.content}</h3>
{showBottomBar && <BottomBar />}
<h5>{children.txt}</h5>
<button onClick={children.btnClick}>点我</button>
</div>
);
}
}
Redux
Redux是负责组织state的工具,提供可预测的状态管理,不仅仅是用于React。下面我们就来了解一下什么场景下使用redux,redux应该怎么使用👣👣
- 💫 使用场景
- 有着相当大量的、随着时间变化的数据;
- state需要有一个单一可靠数据来源;
- 把所有state放在最顶层组件中无法满足需求;
- 某个组件的状态需要共享...
- 💫 如何使用
安装Redux
npm install redux --save
🌰累加器🌰
- 需要一个store来存储数据;
- store里的reducer初始化state并定义state修改规则;
- 通过dispatch一个action来提交对数据的更改;
- action提交到reducer函数里,根据传入的action的type,返回新的state
创建store,src/store/ReduxStore.js
import { createStore } from "redux";
// 定义state初始化和修改规则,reducer是一个纯函数
function counterReducer(state = 0, action) {
// console.log("state", state);
switch (action.type) {
case "ADD":
return state + 1;
case "MINUS":
return state - 1;
default:
return state;
}
}
const store = createStore(counterReducer);
export default store;
创建ReduxPage
import React, { Component } from "react";
import store from "../store/";
export default class ReduxPage extends Component {
componentDidMount() {
// 订阅状态变更
// store.subscribe(() => {
// console.log("state发生变化了");
// this.forceUpdate(); // 强制刷新
// });
}
render() {
console.log("store", store);
return (
<div>
<h3>ReduxPage</h3>
<p>{store.getState()}</p>
<button
onClick={() => {
store.dispatch({ type: "ADD" });
}}
>
add
</button>
</div>
);
}
}
在src/index.js的render里订阅状态变更
import React from "react";
import ReactDOM from "react-dom";
import "./index.css";
import App from "./App";
import store from "./store";
ReactDOM.render(<App />, document.getElementById("root"));
store.subscribe(() => {
console.log("state更新了");
ReactDOM.render(<App />, document.getElementById("root"));
});
😇小结一下😇
- 通过createStore创建store
- reducer初始化、修改状态函数
- getState 获取状态值
- dispatch 提交更新
- subscribe 变更订阅
react-redux
react-redux从名字上来看就是为react量身定做的,接下来我们来了解一下如何安装和如何使用
react-redux
- 安装
npm install react-redux --save
- 使用
react-redux提供来两个api:
💦 1.Provider 为后代组件提供store
💦 2. connect 为组件提供数据和变更方法
全局提供store,index.js
import React from "react";
import ReactDOM from "react-dom";
import "./index.css";
import App from "./App";
import store from "./store";
import { Provider } from "react-redux";
ReactDOM.render(
// 通过Provider跨层级传递把store传给后代
<Provider store={store}>
<App />
</Provider>,
document.getElementById("root")
);
获取状态数据,ReactReduxPage.js
import React, { Component } from "react";
import { connect } from "react-redux";
export default connect(
// mapStateToProps 状态映射 把state映射到props
(state) => ({ num: state }),
// mapDispatchToProps 派发事件映射 把dispatch映射到props)
{
add: () => ({ type: "ADD" }),
}
)(
class ReactReduxPage extends Component {
render() {
const { num, dispatch, add } = this.props;
console.log("props", this.props);
return (
<div>
<h3>{num}</h3>
<h3>ReactReduxPage</h3>
{/* <button onClick={() => dispatch({ type: "ADD" })}>add</button> */}
{/* 方法比较多的时候*/}
<button onClick={add}>add</button>
</div>
);
}
}
);
🌈 另一种写法
class ReactReduxPage extends Component {
render() {
const { num, add, minus } = this.props;
return (
<div>
<h3>{num}</h3>
<h3>ReactReduxPage</h3>
<button onClick={minus}>minus</button>
<button onClick={add}>add</button>
</div>
);
}
}
// mapStateToProps 状态映射 把state映射到props
const mapStateToProps = (state) => {
return {
num: state,
};
};
// mapDispatchToProps 派发事件映射 把dispatch映射到props)
const mapDispatchToProps = {
add: () => {
return { type: "ADD" };
},
minus: () => {
return { type: "MINUS" };
},
};
// connect中的参数:state映射和事件映射
export default connect(mapStateToProps, mapDispatchToProps)(ReactReduxPage);
react-router
react-router包含三个库,分别
react-router
、react-router-dom
和react-native
。
❄️ react-router提供最基本的路由功能,实际应用我们不会直接安装react-router,而是根据应用运行的环境选择安装react-router-dom
(在浏览器中使用)或react-router-native
(在rn中使用)
❄️react-router-dom
和react-router-native
都依赖react-router,所以在安装时,react-router也会自动安装,创建web应用
- 安装
npm install --save react-router-dom
- 基本使用
react-router中奉行一切皆组件的思想
🦴 路由器 ->Router
、
🦴 链接 ->Link
、
🦴 路由 ->Route
、
🦴 独占 ->Switch
、
🦴 重定向 ->Redirect
🦴 ...
🐶 都是以组件形式存在
- 举个🌰
// RouterPage.js
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>
{/* 渲染的三种方式,优先级 children > component > render */}
<Route
exact
path="/"
component={HomePage}
children={() => <div>child</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>
);
}
}
Route渲染内容的三种方式:
🎗 优先级:children > component > render 这三种是互斥的关系🧷 children: func
不管location是否匹配,你都需要渲染一些内容,这时候可以用children,除此之外和render的工作方法一样
🧷 render: func
用render的时候,调用的只是一个函数,只有在location匹配的时候渲染
🧷 component: component
只有在location匹配的时候渲染
- 404页面
为了更好的用户体验,设定一个没有path的路由在路由列表最后面,表示一定匹配
// RouterPage.js
import React, { Component } from "react";
import { BrowserRouter as Router, Route, Link, Switch } from "react-router-dom";
export default class RouterPage extends Component {
render() {
return (
<div>
<h3>RouterPage</h3>
{/* 独占路由:添加Switch表示仅匹配一个 */}
<Switch>
<Router>
<Link to="/">首页</Link>
<Link to="/user">用户中心</Link>
{/* 渲染的三种方式,优先级 children > component > render */}
{/* 根路由要添加exact,实现精确匹配 */}
<Route
exact
path="/"
component={HomePage}
children={() => <div>child</div>}
render={() => <div>render</div>}
/>
<Route path="/user" component={UserPage} />
<Route component={EmptyPage} />
</Router>
</Switch>
</div>
);
}
}
class HomePage extends Component {
render() {
return (
<div>
<h3>HomePage</h3>
</div>
);
}
}
class UserPage extends Component {
render() {
return (
<div>
<h3>UserPage</h3>
</div>
);
}
}
class EmptyPage extends Component {
render() {
return (
<div>
<h3>EmptyPage-404</h3>
</div>
);
}
}
PureComponent
PureComponent -> 纯组件
- 实现性能优化(浅比较)
缺点:必须要用class形式,而且要注意是浅比较即仅作对象的浅层⽐较,如果对象中包含复杂的数据结构,则有可能因为⽆法检查深层的差别,产⽣错误的⽐对结果。所以仅在你的props 和 state 较为简单时,才使⽤ React.PureComponent ,或者在深层数据结构发⽣变化时 调⽤ forceUpdate() 来确保组件被正确地更新
import React, { Component, PureComponent } from "react";
// PureComponent 内置了shouldComponentUpdate的比较
export default class PureComponentPage extends PureComponent {
constructor(props) {
super(props);
this.state = {
// 可实现性能优化
count: 0,
// 实现不了,PureComponent只能实现浅比较
obj: {
num: 1,
},
};
}
setCount = () => {
this.setState({
count: 1000,
obj: {
num: 1000,
},
});
};
// shouldComponentUpdate(nextProps, nextState) {
// // 性能优化,只有值变化了才会更新
// return nextState.count !== this.state.count;
// }
render() {
const { count } = this.state;
console.log("render");
return (
<div>
<h3>PureComponentPage</h3>
<button onClick={this.setCount}>{count}</button>
</div>
);
}
}
- PureComponent于Component的比较
PureComponent
和Component
很相似。区别在于React.Component并未实现shouldComponentUpdate,而PureComponent中以浅层对比prop
和state
的方式实现了该函数。
网友评论