快速开始
npx create-react-app my-app // 创建项目
cd my-app // 进入项目
npm start // 启动项目
npm run eject // 配置暴露项
入口文件->index.js
// 注意: 只要使用jsx语法就要引入react
// ReactDOM.render把组件挂载
import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './App';
ReactDOM.render(
<App />,
document.getElementById('root')
);
// React负责逻辑控制,数据 -> VDOM
// ReactDom渲染实际DOM,VDOM -> DOM
// React使⽤用JSX来描述UI
// babel-loader把JSX 编译成相应的 JS 对象
// React.createElement再把这个JS对象构造成React需要的虚拟dom
入口文档定义
entry: [
// WebpackDevServer客户端,它实现开发时热更更新功能
isEnvDevelopment && require.resolve('react-dev-utils/webpackHotDevClient'),
// 应⽤程序⼊口:src/index
paths.appIndexJs,
// 在path文件可以找到 appIndexJs: resolveModule(resolveApp, 'src/index'),
].filter(Boolean),
webpack配置文件
webpack.config.js 是webpack配置⽂文件,开头的常量量声明可以看出cra能够⽀支持ts、sass及css模块化
// 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)$/;
jsx语法
- 表达式{}的使⽤用
const name = "react study";
const jsx = <div>hello, {name}</div>;
- 函数表达式
const obj = {
fistName: "曾",
lastName: "小白"
};
function formatName(name) {
return name.fistName + " " + name.lastName;
}
const jsx = <div>{formatName(user)}</div>;
- 对象表达式
const greet = <div>good</div>;
const jsx = <div>{greet}</div>;
- 条件语句
const show = true;
const greet = <div>good</div>;
const jsx = (
<div>
{show ? greet : "登录"}
{show && greet}
</div>
);
- 数组表达式
const a = [0, 1, 2];
const jsx = (
<div>
<ul>
{
a.map(item => (
<li key={item}>{item}</li>
)
)
}
</ul>
</div>
)
// diff时候,⾸首先⽐比较type,然后是key,所以同级同类型元素,key值必须得 唯⼀一
- 属性的使⽤用
import logo from "./logo.svg";
const jsx = (
<div>
// 属性:静态值⽤用双引号,动态值⽤用花括号;class、for等要特殊处理理。
<img src={logo} style={{ width: 100 }} className="img" />
</div>
);
- 模块化
// css模块化,创建index.module.css,index.js
import style from "./index.module.css";
<img className={style.logo} />
组件(实现一个定时器)
概念:类似于 JavaScript 函数。它接受任意的⼊入参(即 “props”),并返回⽤用于描述⻚页⾯面展示 内容的 React 元素。
组件有两种形式:class组件和function组件。
- class组件(class组件通常拥有状态和⽣生命周期,继承于Component,实现render⽅方法)
import React, {Component} from 'react';
class Clock extends Component {
constructor(props) {
super(props);
this.state = {
time: new Date()
}
}
// 组件挂载完成之后执行
componentDidMount() {
this.timer = setInterval(() => {
this.setState({
time: new Date()
})
}, 1000)
}
// 组件卸载之前执行
componentWillUnmount() {
clearInterval(this.timer)
}
render() {
const { time } = this.state
return (
<div>
{time.toLocaleString()}
</div>
);
}
}
export default Clock;
- function组件(函数组件通常⽆无状态,仅关注内容展示,返回渲染结果即可,但是从React16.8开始引⼊入了了hooks,函数组件也能够拥有状态,此时 useEffect Hook可以看作componentDidMount , componentDidUpdate 和 componentWillUnmount )
import React, {useState, useEffect} from 'react';
function Clock(props) {
const [ time, setTime ] = useState(new Date());
useEffect(() => {
// 执行的毁掉函数相当于componentDidMount,
// return执行的毁掉函数相当于componentWillUnmount,
// 最后的是依赖项[],相当于componentDidUpdate
const timer = setInterval(() => {
setTime(new Date());
}, 1000);
return () => clearInterval(timer)
},[])
return (
<div>
{time.toLocaleString()}
</div>
);
}
export default Clock;
组件复合
有些组件无法提前知晓它们子组件的具体内容。在 Sidebar(侧边栏)和 Dialog(对话框)等展现通用容器(box)的组件中特别容易遇到这种情况。可以使用一个特殊的 children prop 来将他们的子组件传递到渲染结果中
- 直接使用props.children渲染插入的全部节点
function FancyBorder(props) {
return (
<div className={'FancyBorder FancyBorder-' + props.color}>
{props.children}
</div>
);
}
别的组件可以通过 JSX 嵌套,将任意组件作为子组件传递给它们。
function WelcomeDialog() {
return (
<FancyBorder color="blue">
<h1 className="Dialog-title">
Welcome
</h1>
<p className="Dialog-message">
Thank you for visiting our spacecraft!
</p>
</FancyBorder>
);
}
- 不使用 children,将所需内容传入 props,并使用相应的 prop组件。类似vue中的slot
function SplitPane(props) {
return (
<div className="SplitPane">
<div className="SplitPane-left">
{props.left}
</div>
<div className="SplitPane-right">
{props.right}
</div>
</div>
);
}
function App() {
return (
<SplitPane
left={
<Contacts />
}
right={
<Chat />
} />
);
}
- 使用props中相应的prop值
function Dialog(props) {
return (
<FancyBorder color="blue">
<h1 className="Dialog-title">
{props.title}
</h1>
<p className="Dialog-message">
{props.message}
</p>
</FancyBorder>
);
}
function WelcomeDialog() {
return (
<Dialog
title="Welcome"
message="Thank you for visiting our spacecraft!" />
);
}
redux
- 安装redux
npm install redux --save
- 创建一个store---src/store/index.js
// 1. 需要⼀一个store来存储数据
// 2. store里面的reducer初始化state并定义state修改规则
// 3. dispatch派发一个action来提交对数据的修改
// 4. action作为reduce的第二个参数传入,根据action的type,返回新的state
import { createStore } from 'redux'
// 定义state的初始化和规则, reducer是一个纯函数
function reducer(state=0, action) {
switch (action.type) {
case "ADD":
return state+1;
case "DEL":
return state-1;
default:
return state
}
}
const store=createStore(reducer)
export default store
- 最外层订阅 --- src/index.js
import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './App';
import './static/iconfont/iconfont.css'
import store from './store'
// ReactDOM.render(
// <App />,
// document.getElementById('root')
// );
const render = ()=>{
ReactDOM.render(
<App/>,
document.querySelector('#root')
)
}
render()
store.subscribe(render)
- 界面中使用
import React, {Component} from 'react';
import store from '../../store'
class ReduxPage extends Component {
// 如果没有在index.js中订阅,则需要在这里订阅
// componentDidMount() {
// // 订阅
// store.subscribe(() => {
// // 强制刷新
// this.forceUpdate()
// })
// }
render() {
return (
<div>
ReduxPage
<p>
{store.getState()}
</p>
<button onClick={() => store.dispatch({type: 'ADD'})}>ADD</button>
<button onClick={() => store.dispatch({type: 'DEL'})}>DEL</button>
</div>
);
}
}
export default ReduxPage;
- 总结redux
1. createStore 创建store
2. reducer 初始化、修改状态函数 3. getState 获取状态值
4. dispatch 提交更更新
5. subscribe 变更更订阅
react-redux
- 安装
npm install react-redux --save
- 创建一个store---src/store/index.js跟redux一样
// react-redux提供两个api
// 1. provider为后代组件提供store
// 2. connect为组件提供数据和变更方法
import { createStore } from 'redux'
// 定义state的初始化和规则, reducer是一个纯函数
function reducer(state=0, action) {
switch (action.type) {
case "ADD":
return state+1;
case "DEL":
return state-1;
default:
return state
}
}
const store=createStore(reducer)
export default store
- 最外层引入--- src/index.js
import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './App';
import './static/iconfont/iconfont.css'
import store from './store'
import {Provider} from 'react-redux'
// 用react-redux提供的Provider方法包裹最外层app,并把store传递进去
ReactDOM.render(
<Provider store={store}>
<App />
</Provider>,
document.getElementById('root')
);
- 页面使用(使用connect把页面和状态联系起来)
import React, {Component} from 'react';
import {connect} from "react-redux";
class ReactReduxPage extends Component {
render() {
const {num, add, del} = this.props;
return (
<div>
ReactReduxPage
<p>
{num}
</p>
<button onClick={add}>ADD</button>
<button onClick={del}>DEL</button>
</div>
);
}
}
// //状态映射 mapStateToProps,把状态映射到props上
const mapStateToProps = state => {
return {
num: state
}
}
// 派发事件映射,把事件映射到props上,默认是把所有事件映射到props
const mapDispachToProps = {
add: () => {
return {type: 'ADD'}
},
del: () => {
return {type: 'DEL'}
},
}
// connect接收两个参数,所以它其实是一个高阶组件
// 第一个参数里面mapStateToProps是一个函数返回state,mapDispachToProps是一个对象,返回方法
// 第二个参数是当前页面
export default connect(mapStateToProps, mapDispachToProps)(ReactReduxPage);
react-router
- 概念: react-router包含3个库,react-router、react-router-dom和react-router-native。实际使用中我们会根据实际需求安装,react-router-dom(在浏览器器中使⽤用)或react-router-native(在rn中使⽤用),react-router-dom和 react-router-native都依赖react-router,所以在安装时,react-router也会⾃自动安装。
- 安装
npm install --save react-router-dom
- 组件思想 react-router奉行一切皆组件的气象,路由器--Router,链接---Link,路由---Route,独占路由---Switch,重定向---Redirect都以组件形式存在。
- 使用
import React, {Component} from 'react';
import {BrowserRouter as Router, Route, Link, Switch} from "react-router-dom";
import IndexPage from '../IndexPage'
import UserPage from '../UserPage'
import EmptyPage from '../EmptyPage'
// Router包裹的Link相当于a标签,to指向跳转路径
// Router包裹的Route,path是路径,component是路径匹配的页面
// exact 精确匹配
// EmptyPage写在所有路由的最后面,当前面匹配不上的时候,匹配EmptyPage
// Switch表示独占路由,只渲染一个
class RouterPage extends Component {
render() {
return (
<div>
<Router>
<Link to='/'>首页</Link>
<Link to='/user'>用户中心</Link>
<Switch>
<Route
exact
path='/'
component={IndexPage}
/>
<Route path='/user' component={UserPage} />
<Route component={EmptyPage} />
</Switch>
</Router>
</div>
);
}
}
export default RouterPage;
- Route渲染内容有三种⽅方式
// 1. Route渲染优先级:children>component>render。并且这三种⽅方式互斥,只能⽤用一种。
// 2. children:func,不不管location是否匹配,你都需要渲染⼀一些内容,这时可以用children。
// 3. component: component,只在当location匹配的时候渲染
// 4. render:func,只在当location匹配的时候渲染
<Route
exact
path='/'
component={IndexPage}
children={()=> <div>children</div>}
render={()=> <div>render</div>}
/>
PureComponent
- 实现性能优化,PureComponent内部实现了shouldComponentUpdate方法
- 内部的比较是一个浅比较, React.PureComponent 中以浅层对比 prop 和 state 的⽅式来实现了shouldComponentUpdate() 方法
- PureComponent如果有object形式的数据,还是会在数据改变的时候每次都重新渲染
- 写PureComponent必须是class类型的组件页面
import React, {Component,PureComponent} from 'react';
class PureComponentPage extends PureComponent {
constructor(props){
super(props);
this.state={
count: 0
}
}
setCount=() => {
this.setState({
count: 10
})
}
// PureComponent内部实现了shouldComponentUpdate方法
// 如果不使用PureComponent则需要手动实现是否重新加载
// shouldComponentUpdate(nextProps, nextState, nextContext) {
// return nextState.count!==this.state.count
// }
render() {
console.log('render')
const {count} = this.state
return (
<div>
<button onClick={this.setCount}>{count}</button>
</div>
);
}
}
export default PureComponentPage;
注意:
- React.PureComponent 中的 shouldComponentUpdate() 仅作对象的浅层⽐比较。如果对象中 包含复杂的数据结构,则有可能因为⽆无法检查深层的差别,产⽣生错误的⽐比对结果。仅在你的 props 和 state 较为简单时,才使⽤用 React.PureComponent ,或者在深层数据结构发⽣生变化时 调⽤用 forceUpdate() 来确保组件被正确地更更新。
- React.PureComponent 中的 shouldComponentUpdate() 将跳过所有⼦子组件树的 prop 更更新。因此,请确保所有⼦子组件也都是“纯”的组件。
认识Hook
概念: 在编写函数组的时候使用,Hook 是一个特殊的函数,它可以让你“钩⼊” React 的特性。
- 使用useState Hook(使用useState进行状态管理)
import React, {useState,useEffect} from 'react';
export default function HookPage(props) {
// 声明⼀一个叫 num 的 state 变量量,初始化为0
// setNum接收新的state,然后将状态更改为 'newState' 并触发重新渲染
const [num, setNum] = useState(0)
return (
<div>
HookPage
<p>{num}</p>
<button onClick={()=>setNum(num+1)}>add</button>
</div>
)
}
- 使用 Effect Hook
// 接收2个参数,第一个参数是一个函数
// 函数体相当于componentDidMount,和componentDidUpdate,可以执行一些订阅操作
// 函数return返回一个清除函数,防止内存泄漏,相当于componentWillUnmount
// 第二个参数数组里的数据effect的执行条件,只有当数组里面的值改变的时候才会创建新的订阅
import React, {useState, useEffect} from 'react';
export default function HookPage(props) {
const [time, setTime] = useState(new Date())
useEffect(() => {
const timer = setInterval(() => setTime(new Date()), 1000)
return () => clearInterval(timer)
}, [])
return (
<div>
HookPage
<p>{time.toLocaleTimeString()}</p>
</div>
)
}
自定义Hook与Hook使⽤用规则
- 概念: 自定义Hook是一个函数,名称以'use'开头,函数内部可以调用其他hook。目的是为了在组件之间重用一些状态
import React, {useState, useEffect} from 'react';
export default function HookPage(props) {
return (
<div>
HookPage
<p>{useClock().toLocaleTimeString()}</p>
</div>
)
}
function useClock() {
const [time, setTime] = useState(new Date())
useEffect(() => {
const timer = setInterval(() => setTime(new Date()), 1000)
return () => clearInterval(timer)
}, [])
return time
}
- 使用规则: 只能在函数最外层调⽤Hook。不要在循环、条件判断或者子函数中调⽤。只能在 React 的函数组件中和自定义Hook中调⽤用 Hook。
Hook API之useMemo与useCallback
- useMemo(() => fn, deps)
// 1. 接收2个参数,
// 2. 第一个参数是函数,
// 3. 第二个参数是数组作为依赖项,它仅会在依赖项改变的时候才会重新计算,有助于避免在每次渲染时都进⾏高开销的计算
import React, {useState, useEffect, useMemo} from 'react';
export default function UseMemoPage(props) {
const [num, setNum] = useState(0)
const [value, setValue] = useState('')
const expensive = useMemo(()=> {
console.log('computed')
let sum = 0;
for(let i=0; i<num; i++) {
sum += i;
}
return sum
// 只有num改变的时候,当前函数才会重新执行
},[num])
return (
<div>
<h1>UseMemoPage</h1>
<p>num: {num}</p>
<p>expensive: {expensive}</p>
<button onClick={() => setNum(num+1)}>add</button>
<input value={value} onChange={(e) => setValue(e.target.value)}/>
</div>
)
}
- useCallback(fn, deps)
// 1. 接收2个参数,
// 2. 第一个参数是内联回调函数,
// 3. 第二个参数是数组作为依赖项,它仅会在依赖项改变的时候前面的回调函数才会重新更新,有助于避免在每次渲染时都进⾏高开销的计算
// 4. 当你把回调函数传递给经过优化(例如shouldComponentUpdate)的子组件,它将⾮常有⽤
import React, {useState, useCallback, PureComponent} from 'react';
export default function UseMemoPage(props) {
const [num, setNum] = useState(0)
const add = useCallback(() => {
let sum = 0;
for (let i = 0; i < num; i++) {
sum += i;
}
return sum
// 只有num改变的时候,当前函数才会重新执行
}, [num])
return (
<div>
<h1>UseMemoPage</h1>
<p>num: {num}</p>
<button onClick={() => setNum(num + 1)}>add</button>
<Child addClick={add}/>
</div>
)
}
class Child extends PureComponent {
render() {
const {addClick} = this.props
return (
<div>
<h1>ChildPage</h1>
<button onClick={() => console.log('Child', addClick())}>childAdd</button>
</div>
)
}
}
Antd基础配置项
yarn add antd
- 试⽤用 ant-design组件库
import React, { Component } from 'react'
import Button from 'antd/es/button'
import "antd/dist/antd.css"
class App extends Component {
render() {
return (
<div className="App">
<Button type="primary">Button</Button>
</div>
) }
}
export default App
- 配置按需加载--1安装插件
// react-app-rewired 对 create-react-app 的默认配置进⾏行行⾃自定义
// customize-cra 为了支持 react-app-rewired@2.x 版本
// babel-plugin-import 是⼀一个⽤用于按需加载组件代码和样式的 babel 插件
yarn add react-app-rewired customize-cra babel-plugin-import
自定义主题
// ⾃自定义主题需要⽤用到 less 变量量覆盖功能
// 这里要注意less-loader的版本,最新的版本会报错,我用的5.0.0
yarn add less less-loader
根目录创建--- config-overrides.js
const {override, fixBabelImports, addLessLoader} = require("customize-cra");
module.exports = override(
fixBabelImports("import", {
// antd按需加载
libraryName: "antd",
libraryDirectory: "es",
style: true
}),
addLessLoader({
javascriptEnabled: true,
modifyVars: {"@primary-color": "red"} // 修改主题色
})
);
修改package.json
"scripts": {
"start": "react-app-rewired start",
"build": "react-app-rewired build",
"test": "react-app-rewired test",
"eject": "react-app-rewired eject"
},