重拾React框架学习笔记总结
环境搭建
nvm用来管理node版本。
brew install nvm
nvm ls-remote // 查看所有的node可用版本
nvm list // 查看已安装node版本
nvm install 版本号 // 下载指定node版本,如nvm install v11.14.0
nvm use 版本号 // 使用指定版本
nvm alias default // 设置默认版本,每次启动终端都使用该版本
yarn管理依赖包
npm install -g yarn
在国内,某些情况使用npm和yarn可能无法正常安装一个库,这个时候我们可以选择使用cnpm npm install -g cnpm --registry=https://registry.npm.taobao.org
React脚手架
npm install -g create-react-app
// 创建一个项目
create-react-app demo_app
// 启动项目
cd demo_app
yarn start
知识点
1、函数组件和类组件 2、有状态组件和无状态组件 3、展示型组件和容器型组件
类组件的定义有如下要求: 组件的名称是大写字符开头(无论类组件还是函数组件) 类组件需要继承自React.Component 类组件必须实现render函数
函数组件是使用function来进行定义的函数,只是这个函数会返回和类组件中render函数返回一样的内容。 函数组件有自己的特点: 没有生命周期,也会被更新并挂载,但是没有生命周期函数 没有this(组件实例) 没有内部状态(state)
constructor() {
super();
}
render() {
return (<div></div>)
}
componentDidMount() {
}
componentDidUpdate(prevProps, prevState, snapshot) {
}
componentWillUnmount() {
}
1、props传值 2、回调函数 3、Context 4、事件总线events
传递给子组件的数据,有时候我们希望进行验证。 1、如果你项目中默认继承了Flow或者TypeScript,那么直接就可以进行类型验证。 2、通过prop-types 库来进行参数验证。 3、如果没有传递,希望有默认值,可以使用defaultProps
import PropTypes from 'prop-types';
static propTypes = {
name: PropTypes.string
}
static defaultProps = {
name: "default"
}
1、在setState的回调函数中获取
this.setState({
index: index
}, () => {
console.log("获取setState异步结果 index = " + this.state.index);
})
2、在生命周期方法中获取componentDidUpdate
componentDidUpdate() {
console.log("获取setState异步结果 index = " + this.state.index);
}
setState一定是异步的吗? 其实分成两种情况: 在组件生命周期或React合成事件中,setState是异步; 在setTimeout或者原生dom事件中,setState是同步;
1、通过控制shouldComponentUpdate的返回值来判断是否需要重新渲染页面 2、PureComponent组件默认实现了shouldComponentUpdate 3、高阶memo组件(可以包裹函数组件进行渲染优化)
在React的开发模式中,通常情况下不需要、也不建议直接操作DOM原生,但是某些特殊的情况,确实需要获取到DOM进行某些操作: 1、管理焦点,文本选择或媒体播放 2、触发强制动画 3、集成第三方DOM库
创建ref的三种方式 1、传入一个对象 2、传入一个函数
import React, { Component, createRef } from 'react'
constructor() {
super()
// 方法一:
this.testRef = createRef();
// 方法二:
this.testRef2 = null;
}
render() {
return (
<div>
// 方法一:
<div ref={this.testRef}>获取ref</div>
// 方法二:
<div ref={arg => this.testRef2 = arg}>获取ref2</div>
</div>
)
}
// 方法一
console.log(this.testRef.current);
// 方法二
console.log(this.testRef2);
定义:高阶组件是一个函数,参数是一个组件,返回值是一个组件
因为函数式组件没有实例,所以不能获取到对应的组件对象. 在开发中我们可能想要获取函数式组件中某个元素的DOM,可以通过forwardRef高阶函数;
import React, { Component, createRef, forwardRef } from 'react'
const Home = forwardRef(function(props, ref) {
return (
<div>
<div ref={ref}>ref div</div>
</div>
)
});
export default class App extends Component {
constructor(props) {
super();
this.devRef = createRef();
}
render() {
return (
<div>
<Home ref={this.devRef}/>
<button onClick={e => this.getRef()}>获取ref</button>
</div>
)
}
getRef() {
console.log(this.devRef.current);
}
}
在之前的开发中,我们总是在一个组件中返回内容时包裹一个div元素. 我们又希望可以不渲染这样一个div应该如何操作呢? 使用Fragment,Fragment 允许你将子列表分组,而无需向 DOM 添加额外节点;
React还提供了Fragment的短语法: 它看起来像空标签 <> </>; 但是,如果我们需要在Fragment中添加key,那么就不能使用短语法
render() {
return (
<Fragment key="fragment">
<Home ref={this.devRef}/>
<button onClick={e => this.getRef()}>获取ref</button>
</Fragment>
)
}
render() {
return (
<>
<Home ref={this.devRef}/>
<button onClick={e => this.getRef()}>获取ref</button>
</>
)
}
1、内联样式
内联样式是官方推荐的一种css样式的写法: style 接受一个采用小驼峰命名属性的 JavaScript 对象,,而不是 CSS 字符串; 并且可以引用state中的状态来设置相关的样式;
内联样式的优点: 1.内联样式, 样式之间不会有冲突 2.可以动态获取当前state中的状态
内联样式的缺点: 1.写法上都需要使用驼峰标识 2.某些样式没有提示 3.大量的样式, 代码混乱 4.某些样式无法编写(比如伪类/伪元素)
<h2 style={{fontSize: "50px", color: "red"}}>标题</h2>
2、普通的CSS
普通的css我们通常会编写到一个单独的文件,之后再进行引入。 这样的编写方式和普通的网页开发中编写方式是一致的: 1、如果我们按照普通的网页标准去编写,那么也不会有太大的问题; 2、但是组件化开发中我们总是希望组件是一个独立的模块,即便是样式也只是在自己内部生效,不会相互影响; 3、但是普通的css都属于全局的css,样式之间会相互影响; 4、这种编写方式最大的问题是样式之间会相互层叠掉;
3、css modules
React的脚手架已经内置了css modules的配置: 1、.css/.less/.scss 等样式文件都修改成 .module.css/.module.less/.module.scss 等; 2、之后就可以引用并且进行使用了; 3、css modules确实解决了局部作用域的问题。
但是这种方案也有自己的缺陷: 1、引用的类名,不能使用连接符(.home-title),在JavaScript中是不识别的; 2、所有的className都必须使用{style.className} 的形式来编写; 3、不方便动态来修改某些样式,依然需要使用内联样式的方式;
// style.module.css文件
.title {
color: blue;
}
// 引入
import appStyle from './style.module.css';
export default class App extends PureComponent {
render() {
return (
<div id="app">
<h2 className={appStyle.title}>title</h2>
</div>
)
}
}
4、CSS-in-JS
利用第三方库styled-components
包括主题配置、项目目录别名配置等。
antd主题配置参考链接
1、安装插件
yarn add @craco/craco
2、在项目根目录创建craco.config.js文件
3、修改package.json文件内容
"scripts": {
// "start": "react-scripts start",
// "build": "react-scripts build",
// "test": "react-scripts test",
"start": "craco start",
"build": "craco build",
"test": "craco test",
"eject": "react-scripts eject"
},
4、在craco.config.js配置相关内容
const path = require("path");
const resolve = dir => path.resolve(__dirname, dir);
module.exports = {
webpack: {
// 别名配置
alias: {
'@': resolve("src"),
'pages': resolve("src/pages")
}
}
}
过渡动画的使用 github介绍 1、添加库依赖 yarn add react-transition-group 2、CSSTransition使用
import React, { Component } from 'react'
import { CSSTransition } from 'react-transition-group'
export default class TransitionDemo extends Component {
constructor() {
super();
this.state = {
inProp: false
}
}
render() {
const { inProp } = this.state;
return (
<div>
<CSSTransition
in={inProp}
timeout={2000}
classNames="my-node"
unmountOnExit={true}
onEnter={el => console.log("开始进入")}
onEntering={el => console.log("正在进入")}
onEntered={el => console.log("进入完成")}
onExit={el => console.log("开始退出")}
onExiting={el => console.log("退出状态")}
onExited={el => console.log("退出完成")}>
<div className="my-node-style">
{"I'll receive my-node-* classes"}
</div>
</CSSTransition>
<button type="button" onClick={() => this.changeState()}>
Click to Enter
</button>
</div>
);
}
changeState() {
const { inProp } = this.state;
console.log(`changeState = ${inProp}`);
this.setState({
inProp: !inProp
});
}
}
// css
.my-node-enter {
opacity: 0;
}
.my-node-enter-active {
opacity : 1;
transition: opacity 200ms;
}
.my-node-exit {
opacity: 1;
}
.my-node-exit-active {
opacity : 0;
transition: opacity 200ms;
}
.my-node-style {
font-size: 20px;
color : red;
}
1、useCallback
useCallback在什么时候使用? 场景: 在将一个组件中的函数, 传递给子元素进行回调使用时, 使用useCallback对函数进行处理.
import React, {useState, useCallback, memo} from 'react';
const HYButton = memo((props) => {
console.log("HYButton重新渲染: " + props.title);
return <button onClick={props.increment}>HYButton +1</button>
});
export default function CallbackHookDemo02() {
console.log("CallbackHookDemo02重新渲染");
const [count, setCount] = useState(0);
const [show, setShow] = useState(true);
const increment1 = () => {
console.log("执行increment1函数");
setCount(count + 1);
}
// const increment2 = () => {
// console.log("执行increment2函数");
// setCount(count + 1);
// }
const increment2 = useCallback(() => {
console.log("执行increment2函数");
setCount(count + 1);
}, [count]);
return (
<div>
<h2>CallbackHookDemo01: {count}</h2>
{/* <button onClick={increment1}>+1</button>
<button onClick={increment2}>+1</button> */}
<HYButton title="btn1" increment={increment1}/>
<HYButton title="btn2" increment={increment2}/>
<button onClick={e => setShow(!show)}>show切换</button>
</div>
)
}
2、useMemo
使用场景: 1、函数进行大量的计算操作,使用useMemo避免每次渲染时都重现计算 2、对子组件传递相同的对象时,使用useMemo进行性能优化
场景一:
import React, {useState, useMemo} from 'react';
function calcNumber(count) {
console.log("calcNumber重新计算");
let total = 0;
for (let i = 1; i <= count; i++) {
total += i;
}
return total;
}
export default function MemoHookDemo01() {
const [count, setCount] = useState(10);
const [show, setShow] = useState(true);
// MemoHookDemo01每次渲染都会调用calcNumber重新计算
//const total = calcNumber(count);
// useMemo优化只依赖count去重新计算
const total = useMemo(() => {
return calcNumber(count);
}, [count]);
return (
<div>
<h2>计算数字的和: {total}</h2>
<button onClick={e => setCount(count + 1)}>+1</button>
<button onClick={e => setShow(!show)}>show切换</button>
</div>
)
}
场景二:
import React, { useState, memo, useMemo } from 'react';
const HYInfo = memo((props) => {
console.log("HYInfo重新渲染");
return <h2>名字: {props.info.name} 年龄: {props.info.age}</h2>
});
export default function MemoHookDemo02() {
console.log("MemoHookDemo02重新渲染");
const [show, setShow] = useState(true);
// MemoHookDemo02每次渲染导致HYInfo重新渲染
// const info = { name: "why", age: 18 };
// 优化
const info = useMemo(() => {
return { name: "why", age: 18 };
}, []);
return (
<div>
<HYInfo info={info} />
<button onClick={e => setShow(!show)}>show切换</button>
</div>
)
}
3、useRef
useRef返回一个ref对象,返回的ref对象再组件的整个生命周期保持不变。 最常用的ref是两种用法: 用法一:引入DOM(或者组件,但是需要是class组件)元素; 用法二:保存一个数据,这个对象在整个生命周期中可以保存不
用法一:
import React, { useEffect, useRef } from 'react';
class TestCpn extends React.Component {
render() {
return <h2>TestCpn</h2>
}
}
export default function RefHookDemo01() {
const titleRef = useRef();
const inputRef = useRef();
const testRef = useRef();
const testRef2 = useRef();
function changeDOM() {
titleRef.current.innerHTML = "Hello World";
inputRef.current.focus();
console.log(testRef.current);
}
return (
<div>
<h2 ref={titleRef}>RefHookDemo01</h2>
<input ref={inputRef} type="text"/>
<TestCpn ref={testRef}/>
<button onClick={e => changeDOM()}>修改DOM</button>
</div>
)
}
用法二:
import React, { useRef, useState, useEffect } from 'react'
export default function RefHookDemo02() {
const [count, setCount] = useState(0);
const numRef = useRef(count);
useEffect(() => {
numRef.current = count;
}, [count])
return (
<div>
<h2>count上一次的值: {numRef.current}</h2>
<h2>count这一次的值: {count}</h2>
<button onClick={e => setCount(count + 10)}>+10</button>
</div>
)
}
4、useImperativeHandle
useImperativeHandle并不是特别好理解,我们一点点来学习。 我们先来回顾一下ref和forwardRef结合使用: 通过forwardRef可以将ref转发到子组件; 子组件拿到父组件中创建的ref,绑定到自己的某一个元素中;
import React, { useRef, forwardRef } from 'react';
const HYInput = forwardRef((props, ref) => {
return <input ref={ref} type="text"/>
})
export default function ForwardRefDemo() {
const inputRef = useRef();
return (
<div>
<HYInput ref={inputRef}/>
<button onClick={e => inputRef.current.focus()}>聚焦</button>
</div>
)
}
forwardRef的做法本身没有什么问题,但是我们是将子组件的DOM直接暴露给了父组件: 直接暴露给父组件带来的问题是某些情况的不可控; 父组件可以拿到DOM后进行任意的操作; 但是,事实上在上面的案例中,我们只是希望父组件可以操作的focus,其他并不希望它随意操作;
通过useImperativeHandle可以只暴露固定的操作: 通过useImperativeHandle的Hook,将传入的ref和useImperativeHandle第二个参数返回的对象绑定到了一起; 所以在父组件中,使用 inputRef.current时,实际上使用的是返回的对象; 比如我调用了 focus函数,甚至可以调用 printHello函数;
import React, { useRef, forwardRef, useImperativeHandle } from 'react';
const HYInput = forwardRef((props, ref) => {
const inputRef = useRef();
useImperativeHandle(ref, () => ({
focus: () => {
inputRef.current.focus();
}
}), [inputRef])
return <input ref={inputRef} type="text"/>
})
export default function UseImperativeHandleHookDemo() {
const inputRef = useRef();
return (
<div>
<HYInput ref={inputRef}/>
<button onClick={e => inputRef.current.focus()}>聚焦</button>
</div>
)
}
5、useLayoutEffect
useLayoutEffect看起来和useEffect非常的相似,事实上他们也只有一点区别而已: useEffect会在渲染的内容更新到DOM上后执行,不会阻塞DOM的更新; useLayoutEffect会在渲染的内容更新到DOM上之前执行,会阻塞DOM的更新;
如果我们希望在某些操作发生之后再更新DOM,那么应该将这个操作放到useLayoutEffect。
6、自定义Hook
自定义 Hook 是一个函数,其名称以 “use” 开头,函数内部可以调用其他的 Hook。
export default function CustomLifeHookDemo01() {
useLoggingLife("CustomLifeHookDemo01");
return (
<div>
<h2>CustomLifeHookDemo01</h2>
<Home/>
<Profile/>
</div>
)
}
// 自定义生命周期打印
function useLoggingLife(name) {
useEffect(() => {
console.log(`${name}组件被创建出来了`);
return () => {
console.log(`${name}组件被销毁掉了`);
}
}, []);
}
7、react-redux的hook用法
// 未使用hooks
import React, { Component } from 'react'
import { connect } from 'react-redux';
import { changeInputValue } from './store_react_redux/actionCreators';
class App extends Component {
render() {
return (
<div>
<input
placeholder="请输入信息"
style={{ width: "200px", height: '40px', border: '1px solid red' }}
value={this.props.inputValue}
onChange={this.props.handleChangeInput}
/>
</div>
);
}
}
const mapStateToProps = (state) => {
return {
inputValue: state.inputValue
}
}
const mapDispatchToProps = (dispatch) => {
return {
handleChangeInput(e) {
dispatch(changeInputValue(e.target.value));
}
}
}
export default connect(mapStateToProps, mapDispatchToProps)(App);
// 使用hooks
import React, { Component } from 'react'
import { shallowEqual, useDispatch, useSelector } from 'react-redux';
import { changeInputValue } from './store_react_redux/actionCreators';
export default function App() {
const dispatch = useDispatch();
const {inputValue} = useSelector(state => state, shallowEqual);
return (
<div>
<input
placeholder="请输入信息"
style={{ width: "200px", height: '40px', border: '1px solid red' }}
value={inputValue}
onChange={(e) => dispatch(changeInputValue(e.target.value))}
/>
</div>
);
}
SSR(Server Side Rendering,服务端渲染),指的是页面在服务器端已经生成了完成的HTML页面结构,不需要浏览器解析;
对应的是CSR(Client Side Rendering,客户端渲染),我们开发的SPA页面通常依赖的就是客户端渲染;
同构: 一套代码既可以在服务端运行又可以在客户端运行,这就是同构应用。 同构是一种SSR的形态,是现代SSR的一种表现形式。 当用户发出请求时,先在服务器通过SSR渲染出首页的内容。 但是对应的代码同样可以在客户端被执行。 执行的目的包括事件绑定等以及其他页面切换时也可以在客户端被渲染;
React SSR成熟框架 Next.js
官方文档
1、yarn build打包
文件说明:
js文件目录
[hash].chunk.js
代表是所有依赖的第三方库, vendor(第三方库) 的代码;
main.[hash].chunk.js
我们自己编写的应用程序代码;
runtime~main.[hash].js
Webpack runtime逻辑的chunk;
用于加载和运行你的应用程序;
media
一些字体文件和图片
2、打包优化
很多模块,其实没有必要一开始就进行加载,会影响首屏加载速度; 我们可以让某些组件用到时再加载(懒加载); 如何可以让一个组件进行懒加载呢? 使用react给我们提供的lazy函数即可
import Discover from "../pages/discover";
改为
const Discover = React.lazy(_ => import("../pages/discover"));
然后渲染路由的地方增加Suspense标签
<Suspense fallback={renderLoading()}>
{renderRoutes(routes)}
</Suspense>
function renderLoading() {
return <div>loading...</div>
}
|