React
- 特点:
- 组件化、声明式编码;虚拟 DOM + Diffing 算法,减少 DOM 操作
- 可使用 React Navite 开发移动端
// hello word
const root = ReactDOM.createRoot(document.getElementById("root"));
root.render(<h1>Hello, world!</h1>);
基础使用
CDN 引入
<script>
标签的 type 类型,默认为text/javascript
;jsx 应使用text/babel
- 引入顺序: react 核心 --> react-dom --> babel
<body>
<!-- 准备一个“容器” -->
<div id="test"></div>
<!-- 引入react核心库 -->
<script
crossorigin
src="https://unpkg.com/react@16/umd/react.development.js"
></script>
<!-- 引入react-dom,用于支持react DOM 操作 -->
<script
type="text/javascript"
crossorigin
src="https://unpkg.com/react-dom@16/umd/react-dom.development.js"
></script>
<!-- 引入bable.js,用于将jsx转为js -->
<script src="../js/bable.min.js"></script>
<!-- 书写jsx内容 type类型必须为 text/babel -->
<script type="text/babel">
// 创建虚拟DOM,此处不能写引号,他不是字符串而是虚拟DOM
const VDOM = <h1>hello,word</h1>;
// 渲染虚拟DOM到页面 ReactDOM.render(虚拟DOM,html容器)
ReactDOM.render(VDOM, document.getElementById("test"));
</script>
</body>
VSCode 插件
ES7+React/Readux...
- 快捷键:rfc 函数组件、rcc 类式组件、imp、imn 引入模块、样式
常用插件库
react-router-dom
- 有三个版本,web 版 移动版 通用版【当前笔记只记录 web 版】
prop-types 类型限制
qs 路径字符处理
redux 非官方
- redux
- redux-thunk 中间件(封装的函数),用于支持异步 action 的中间件
react-redux 与 redux 功能一致
- react 插件库,官方出品
yarn add redux-thunk
使用 redux 的异步 action
Fragment
- 相当于 vue 中的 template 标签,不会显示在页面上
- 用于在外层包裹标签,也可以写个空标签,作包裹
<Fragment></Fragment>
<></>
- 使用时必须先引入
import React,{Component,Fragment} from react
- 用于在外层包裹标签,也可以写个空标签,作包裹
Context
一种组件间通信方式, 常用于【祖组件】与【后代组件】间通信;在应用开发中一般不用 context, 一般都用它的封装 react 插件
1) 创建Context容器对象:
const XxxContext = React.createContext()
2) 渲染子组时,外面包裹xxxContext.Provider, 通过value属性给后代组件传递数据:
// 这个数据可以是数组、对象、字符串。。。。 {{}} {[]} {'xxx'}
(<xxxContext.Provider value={数据}>
子组件
</xxxContext.Provider>)
3) 后代组件读取数据:
//第一种方式:仅适用于类组件
static contextType = xxxContext // 声明接收context
this.context // 读取context中的value数据
//第二种方式: 函数组件与类组件都可以(直接写在子孙组件中)
(<xxxContext.Consumer>
{
value => ( // value就是context中的value数据
return 要显示的内容
)
}
</xxxContext.Consumer>)
杂项
render 结构的注释
- 借助花括号中是 js 表达式,可使用 js 的 /_ ... _/ 进行注释
jsx 标签样式中,可以使用{},插入 js 表达式
jsx<li style={{ backgroundColor: this.state.mouse ? "#ddd" : "white" }}></li>
jsx 中可能不认识confirm,使用时需要加
window.confirm('确定吗')
三元表达式的嵌套
todos 案例
- default 只在第一次生效,后续更改数据不再变化
- checked 会让按钮固定值,需要配合 change 事件使用
核心知识
虚拟 DOM
- 本质是 Object 类型的对象(一般对象)
- 虚拟 DOM 比较 " 轻",真实 DOM 比较重,因为虚拟 DOM 是 React 内部在用,无需真实 DOM 上那么多属性
- 虚拟 DOM 最终会被 React 转为真实 DOM,呈现在页面上。
JSX 文件
- 语法:
- 定义虚拟 DOM 时,不要写引号
- 标签中混入 JS 表达式时需要使用 {}
- 样式的类名应使用 className 而不是 class,避免与 class 类的定义混淆
- 多个单词的样式,采用驼峰命名法
- 外层花括号表示是一个 js 表达式,内层括号表示是一个对象
- 最外层只能有一个根标签,所有标签必须闭合
- 标签首字母
- 若小写字母开头,则将该标签转为 html 中同名元素,若 html 中无该标签对应的同名元素,则报错!
- 若大写字母开头,react 就去渲染对应的组件,若组件没有定义,则报错!
说明:
不能直接被浏览器解析,需要借助
babel.js
将 jsx 转为 jsJSX 也是一个表达式,编译后会被转为普通 JavaScript 函数调用,并且对其取值后得到 JavaScript 对象,本质就是一个 js 对象。
js// 使用js语句创建虚拟DOM,而不用jsx const VDOM = React.creatElement("h1", { id: "title" }, "123"); // 等同于 jsx 下的如下代码 const VDOM = <h1 id="title"> 123 </h1>; // 使用js语句创建虚拟DOM,而不用jsx const VDOM = React.creatElement( "h1", { id: "title" }, React.creatElement("span", { id: "title" }, 123) ); // 等同于 jsx 下的如下代码 const VDOM = ( <h1 id="title"> <span>123</span> </h1> );
js 表达式与 js 语句的区别
- 表达式:一个表达式会产生一个值,可以放在任何需要的地方
- a+b demo() arr.map() function test(){}
- 语句(代码),不能混入 jsx 虚拟 DOM 中
- if() for(){} switch()
- 表达式:一个表达式会产生一个值,可以放在任何需要的地方
使用:
模板中嵌入表达式
- 花括号内可以放置任何有效的 JavaScript 表达式
jsxconst name = "Josh Perez"; // 模板中使用 {花括号} 插入数据 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>;
在
if
语句和for
循环的代码块中使用 JSX,将 JSX 赋值给变量,把 JSX 当作参数传入,以及从函数中返回 JSX;但不能在虚拟 DOM 中使用这些语句。jsxfunction getGreeting(user) { if (user) { return <h1>Hello, {formatName(user)}!</h1>; } return <h1>Hello, Stranger.</h1>; }
循环遍历渲染
- 每一项都需要有唯一的标识,key 值与 vue 中的类似
- 不能在 jsx 中使用 for if 等语句
jsxconst data = ["abc", "123", "456"]; const VDOM = ( <div> <h1>列表渲染</h1> <ul> {data.map((item, index) => { return <li key="index">{item}</li>; })} </ul> </div> );
组件分类
函数式组件(无状态)
- 函数式组件中,this 默认指向 undefined,
- 原因:babel 编译时,执行 js 严格模式,影响 this 执行
- 组件首字母必须大写,否则会被解析为 html 标签,导致报错!
- 函数式组件 必须有返回值
- render() 函数中,写函数标签,并且首字母大写!
- 执行
ReactDOM.render(......)
的过程- React 解析组件标签,找到 MyComponent 组件
- 发现组件是使用函数定义的,随后调用该函数,将返回的虚拟 DOM 转为真实 DOM,随后呈现在页面中
// 1.创建函数式组件(组件名就是函数名)
function MyComponent() {
console.log(this); // undefined
return <h2>函数自定义组件(适用于【简单组件】)</h2>;
}
// 2.渲染组件到页面
ReactDOM.render(<MyComponent />, document.getElementById("test"));
类式组件(有状态)
- 注意:
- 必须写 render 函数,且必须有返回值
- 执行
ReactDOM.render(......)
的过程- React 解析组件标签,找到 MyComponent 组件
- 发现组件是使用类定义的,随后 new 这个类的实例,并通过该实例调用到原型上的 render 方法
- 将 render 返回的虚拟 DOM 转为真实 DOM,随后呈现在页面中
- render 函数中,this 指向当前组件实例对象
// 1.创建类式组件 继承自 React.Component
class Welcome extends React.Component {
// render 放在 Welcome的原型对象上(Welcome组件实例对象),供实例使用
render() {
return <h1>类式组件,适用于【复杂组件】的定义</h1>;
}
}
// 渲染组件到页面
ReactDOM.render(<Welcome />, document.getElementById("test"));
非受控组件(现用现取)
- 内容现用现取,不经过 state 管理
- 过于依赖视图,频繁使用 ref
//创建组件
class Login extends React.Component {
handleSubmit = (event) => {
event.preventDefault(); // 阻止默认事件 表单提交
const { username, password } = this;
alert(
`你输入的用户名是:${username.value},你输入的密码是:${password.value}`
);
};
render() {
return (
<form onSubmit={this.handleSubmit}>
用户名:
<input ref={(c) => (this.username = c)} type="text" name="username" />
密码:
<input
ref={(c) => (this.password = c)}
type="password"
name="password"
/>
<button>登录</button>
</form>
);
}
}
//渲染组件
ReactDOM.render(<Login />, document.getElementById("test"));
受控组件
- 经过 state 管理的数据,减少 ref 的使用
//创建组件
class Login extends React.Component {
//初始化状态
state = {
username: "", //用户名
password: "", //密码
};
//保存用户名到状态中
saveUsername = (event) => {
this.setState({ username: event.target.value });
};
//保存密码到状态中
savePassword = (event) => {
this.setState({ password: event.target.value });
};
//表单提交的回调
handleSubmit = (event) => {
event.preventDefault(); //阻止表单提交
const { username, password } = this.state;
alert(`你输入的用户名是:${username},你输入的密码是:${password}`);
};
render() {
return (
<form onSubmit={this.handleSubmit}>
用户名:
<input onChange={this.saveUsername} type="text" name="username" />
密码:
<input onChange={this.savePassword} type="password" name="password" />
<button>登录</button>
</form>
);
}
}
//渲染组件
ReactDOM.render(<Login />, document.getElementById("test"));
- 函数柯里化方式,简化上面代码,核心如下:
- saveFormData 函数:通过函数调用继续返回函数的方式,实现多次接收参数最后统一处理的函数编码形式。
class Login extends React.Component {
//保存表单数据到状态中
saveFormData = (dataType) => {
return (event) => {
this.setState({ [dataType]: event.target.value });
};
};
render() {
return (
<form onSubmit={this.handleSubmit}>
用户名:
<input
onChange={this.saveFormData("username")}
type="text"
name="username"
/>
密码:
<input
onChange={this.saveFormData("password")}
type="password"
name="password"
/>
<button>登录</button>
</form>
);
}
}
- 不使用柯里化方式实现
class Login extends React.Component {
// 保存表单数据到状态中 同时接收需要的参数,进行状态设置
saveFormData = (dataType, event) => {
this.setState({ [dataType]: event.target.value });
};
// 传递时: onChange 函数接收返回值中调用 this.saveFormData 并传入所有参数,会立即执行
render() {
return (
<form onSubmit={this.handleSubmit}>
用户名:
<input
onChange={(event) => this.saveFormData("username", event)}
type="text"
name="username"
/>
密码:
<input
onChange={(event) => this.saveFormData("password", event)}
type="password"
name="password"
/>
<button>登录</button>
</form>
);
}
}
组件实例核心属性
state 状态/数据
- state 是组件对象最重要的属性,值是对象(可以包含多个 key-value 的组合)
- 组件被称为"状态机",通过更新组件 state 来更新对应页面的显示(重新渲染组件)
- 复杂写法:在创建组件时配置 constructor
- 必须调用 super 方法
- 通过 this.state ={},配置状态数据
// 1.创建类式组件 继承自 React.Component
class Welcome extends React.Component {
constructor(props) {
// 必须调用super,可以传入props
super(props);
this.state = {
isHot: true,
};
}
// render 放在 Welcome的原型对象上(Welcome组件实例对象),供实例使用
render() {
console.log(this); // this指向当前组件实例对象
return <h1>今天天气:{this.state.isHot ? "炎热" : "寒冷"}</h1>;
}
}
// 渲染组件到页面
ReactDOM.render(<Welcome />, document.getElementById("app"));
简写
class 中可以直接写赋值语句,且默认会放在类的实例对象自身
因此 state 可以不放在 constructor 中,直接放在 class 类中
类中的 this 默认指向组件的实例对象,再结合箭头函数,可简化方法的书写
jsxclass Weather extends React.Component { //初始化状态 state = { isHot: false, wind: "微风" }; render() { const { isHot, wind } = this.state; return ( <h1 onClick={this.changeWeather}> 今天天气很{isHot ? "炎热" : "凉爽"},{wind} </h1> ); } //自定义方法 ———— 使用赋值语句的形式+箭头函数 changeWeather = () => { const isHot = this.state.isHot; this.setState({ isHot: !isHot }); }; }
事件绑定
原生方法
- onclick、addEventListener,都可以正常使用,jsx 支持 js
jsx使用 React 绑定事件
- 为标签绑定时,事件名使用驼峰命名法(例: onClick )
- 事件名写为函数形式,不能是原生的字符串形式,而是花括号的 js 表达式,且不能带小括号立即执行!
- this 指向问题:
- constructor 中的 this 指向 当前组件实例对象
- render 中的 this 指向 当前组件实例对象
- 类的普通方法 中的 this 指向 undefined
- 执行次数
- constructor 构造器调用几次? ——— 1 次
- render 调用几次? —— 1+n 次 1 是初始化的那次 n 是状态 setState 更新的次数
- 自定义函数调用几次? —— 调用几次执行几次
- 修改状态
- 状态(state)不可直接更改,借助 React 的 API 进行更改,且更新是一种合并,不是替换。
this.state.isHot = !isHot
//这是错误的写法this.setState({ isHot: !this.state.isHot });
jsx//1.创建组件 class Weather extends React.Component { constructor(props) { super(props); //初始化状态 this.state = { isHot: false, wind: "微风" }; //解决changeWeather中this指向问题 this.changeWeather = this.changeWeather.bind(this); } //render调用几次? ———— 至少一次,每次状态更新都再次调用 render() { // 读取状态 const { isHot, wind } = this.state; return ( // 这里的this.changeWeather是constructor上的changeWeather <h1 onClick={this.changeWeather}> 今天天气很{isHot ? "炎热" : "凉爽"},{wind} </h1> ); } changeWeather() { //changeWeather放在哪里? ———— Weather的原型对象上,供实例使用 //由于changeWeather是作为onClick的回调,所以不是通过实例调用的,是直接调用 //类中的方法默认开启了局部的严格模式,所以changeWeather中的this为undefined this.setState({ isHot: !this.state.isHot }); console.log(this); } } //2.渲染组件到页面 ReactDOM.render(<Weather />, document.getElementById("test"));
setState 修改数据
- 函数式 setState
- 对象式的 setState
1. setState(stateChange, [callback])------对象式的setState(是函数式的语法糖)
(1).stateChange为状态改变对象(该对象可以体现出状态的更改)
this.staState({key:this.state.key+1},()=>{ console.log(this.state.key) })
(2).callback是可选的回调函数, 它在状态更新完毕、界面也更新后(render调用后)才被调用
回调函数中可以确保获取到最新的数据
2. setState(updater, [callback])------函数式的setState
1.updater为返回stateChange对象的函数。
2.updater可以接收到state和props。
this.staState(state=>({key:state.key+1},()=>{...})
4.callback是可选的回调函数, 它在状态更新、界面也更新后(render调用后)才被调用。
3.使用原则:
(1).如果新状态不依赖于原状态 ===> 使用对象方式
(2).如果新状态依赖于原状态 ===> 使用函数方式
(3).如果需要在setState()执行后获取最新的状态数据,
要在第二个callback函数中读取
props 基本使用
- 在组件标签中 书写属性,配置值进行数据传递
age={18}
如果在标签中传递数字时,使用花括号包裹,不能写成引号字符串
- 在组件的 props 上可以得到对应数据
- props 是只读属性,如果对 props 进行修改会报错!
//创建组件
class Person extends React.Component {
render() {
// console.log(this);
const { name, age, sex } = this.props;
// props是只读的
// this.props.name = 'jack' //此行代码会报错,因为props是只读的
return (
<ul>
<li>姓名:{name}</li>
<li>性别:{sex}</li>
<li>年龄:{age + 1}</li>
</ul>
);
}
}
//渲染组件到页面
ReactDOM.render(
<Person name="jerry" age={19} sex="男" />,
document.getElementById("test1")
);
ReactDOM.render(
<Person name="tom" age={18} sex="女" />,
document.getElementById("test2")
);
const p = { name: "老刘", age: 18, sex: "女" };
// console.log('@',...p);
// ReactDOM.render(<Person name={p.name} age={p.age} sex={p.sex}/>,document.getElementById('test3'))
// 这里的 {...p} 是js表达式的意思,react提供支持进行展开,原生js不支持直接展开
ReactDOM.render(<Person {...p} />, document.getElementById("test3"));
ReactDOM.render(
<Person name="tom" age={18} />,
document.getElementById("test3")
);
函数式组件使用 props
//创建组件
function Person(props) {
const { name, age, sex } = props;
return (
<ul>
<li>姓名:{name}</li>
<li>性别:{sex}</li>
<li>年龄:{age}</li>
</ul>
);
}
Person.propTypes = {
name: PropTypes.string.isRequired, //限制name必传,且为字符串
sex: PropTypes.string, //限制sex为字符串
age: PropTypes.number, //限制age为数值
};
//指定默认标签属性值
Person.defaultProps = {
sex: "男", //sex默认值为男
age: 18, //age默认值为18
};
//渲染组件到页面
ReactDOM.render(<Person name="jerry" />, document.getElementById("test1"));
props 限制
在 React15 及以前,可以通过
React.PropTypes
限制传递的数据类型在 React16 之后,通过引入外部文件 prop-types 实现,减少 React 自身的体积
- defaultProps 默认值限制(键值对形式存在)
- propTypes 类型、必要性限制
- isRequired 必传值
- string 字符串
- func 函数
- number 数字
组件化开发中
需要手动下载
prop-types
这个库 yarn add prop-typesjs// 引入 PropTypes import PropTypes from 'prop-types' //对接收的props进行:类型、必要性的限制 static propTypes = { addTodo:PropTypes.func.isRequired }
<!-- 引入prop-types,用于对组件标签属性进行限制 -->
<script type="text/javascript" src="../js/prop-types.js"></script>
<script type="text/babel">
// Person是创建的class类名
// 对标签属性进行类型、必要性的限制
Person.propTypes = {
name:PropTypes.string.isRequired, //限制name必传,且为字符串
sex:PropTypes.string,//限制sex为字符串
age:PropTypes.number,//限制age为数值
speak:PropTypes.func,//限制speak为函数
}
// 指定默认标签属性值
Person.defaultProps = {
sex:'男',//sex默认值为男
age:18 //age默认值为18
}
// 渲染组件到页面
ReactDOM.render(<Person name={100} speak={speak}/>,document.getElementById('test1'))
ReactDOM.render(<Person name="tom" age={18} sex="女"/>,document.getElementById('test2'))
const p = {name:'老刘',age:18,sex:'女'}
// console.log('@',...p);
// ReactDOM.render(<Person name={p.name} age={p.age} sex={p.sex}/>,document.getElementById('test3'))
ReactDOM.render(<Person {...p}/>,document.getElementById('test3'))
function speak(){
console.log('我说话了');
}
</script>
props 简写
- 将类型限制放在定义类中,借助 static 配置类的静态属性
- 可以直接将 内容放在类自身而不是原型上
- 构造器是否接收 props,是否传递给 super,取决于:是否希望在构造器中通过 this.props 访问 props
import PropTypes from "prop-types";
//创建组件
class Person extends React.Component {
constructor(props) {
//构造器是否接收props,是否传递给super,取决于:是否希望在构造器中通过this访问props
// console.log(props);
super(props);
}
//对标签属性进行类型、必要性的限制
static propTypes = {
name: PropTypes.string.isRequired, //限制name必传,且为字符串
sex: PropTypes.string, //限制sex为字符串
age: PropTypes.number, //限制age为数值
};
//指定默认标签属性值
static defaultProps = {
sex: "男", //sex默认值为男
age: 18, //age默认值为18
};
render() {
const { name, age, sex } = this.props;
return (
<ul>
<li>姓名:{name}</li>
<li>性别:{sex}</li>
<li>年龄:{age + 1}</li>
</ul>
);
}
}
ReactDOM.render(<Person {...p} />, document.getElementById("test3"));
三类 ref
String 型 refs(16.3 前)
与 vue 中 ref 类似,通过 ref 给标签打标识,再借助 refs 可以获取
ref 拿到的是真实 DOM
存在效率上的问题,不建议使用,后期版本可能删除
jsx//创建组件 class Demo extends React.Component { //展示左侧输入框的数据 showData = () => { const { input1 } = this.refs; alert(input1.value); }; //展示右侧输入框的数据 showData2 = () => { const { input2 } = this.refs; alert(input2.value); }; render() { return ( <div> <input ref="input1" type="text" placeholder="点击按钮提示数据" /> <button onClick={this.showData}>点我提示左侧的数据</button> <input ref="input2" onBlur={this.showData2} type="text" placeholder="失去焦点提示数据" /> </div> ); } }
回调型 refs
- React 会自动调用 ref 身上绑定的函数,
- 接收到一个参数,该参数为标签自身
- 借助箭头函数,将该标签绑定到组件身上,再使用
- 数据更新时的细节(无关紧要)
- 当数据刷新时,会重新调用 render 函数,此时通过 ref 绑定的回调会被执行两次
- 第一次返回 undefined;第二次返回真正的结构
- 解决:将函数写在 class 类中,就会不重新执行回调绑定,影响不大
//创建组件
class Demo extends React.Component{
//展示左侧输入框的数据
showData = ()=>{
const {input1} = this
alert(input1.value)
}
//展示右侧输入框的数据
showData2 = ()=>{
const {input2} = this
alert(input2.value)
}
// ref绑定函数外置
saveInput = (c)=>{
this.input1 = c;
console.log('@',c);
}
render(){
return(
<div>
<!-- c => this.input1 = c 相当于 (c)=>{this.input1 = c} -->
{/*<input ref={c => this.input1 = c } type="text" placeholder="点击按钮提示数据"/>*/}
<input ref={this.saveInput} type="text"/><br/><br/>
<button onClick={this.showData}>点我提示左侧的数据</button>
<input onBlur={this.showData2} ref={c => this.input2 = c } type="text" placeholder="失去焦点提示数据"/>
</div>
)
}
}
//渲染组件到页面
ReactDOM.render(<Demo a="1" b="2"/>,document.getElementById('test'))
React.createRef() 16.3 之后
一个容器只能存一个标签,重复使用会把上一次的内容覆盖
使用步骤
- myRef = React.createRef() 创建容器
- ref={this.myRef2} 在标签中绑定 ref
- this.myRef.current 拿到绑定的标签
jsx//创建组件 class Demo extends React.Component { // React.createRef调用后可以返回一个容器,该容器可以存储被ref所标识的节点,该容器是“专人专用”的 myRef = React.createRef(); myRef2 = React.createRef(); //展示左侧输入框的数据 showData = () => { alert(this.myRef.current.value); }; //展示右侧输入框的数据 showData2 = () => { alert(this.myRef2.current.value); }; render() { return ( <div> <input ref={this.myRef} type="text" placeholder="点击按钮提示数据" /> <button onClick={this.showData}>点我提示左侧的数据</button> <input onBlur={this.showData2} ref={this.myRef2} type="text" placeholder="失去焦点提示数据" /> </div> ); } } //渲染组件到页面 ReactDOM.render(<Demo a="1" b="2" />, document.getElementById("test"));
事件处理
通过 onXxx 属性指定事件处理函数(注意大小写)
- React 使用的是自定义(合成)事件, 而不是使用的原生 DOM 事件 ——— 为了更好的兼容性
- React 中的事件是通过事件委托方式处理的(委托给组件最外层的元素) ———为了更高效
可通过 event.target 拿到发生事件的 DOM 元素对象 ——— 不要过度使用 ref
jsxclass Demo extends React.Component { //展示右侧输入框的数据 showData = (event) => { alert(event.target.value); }; render() { return ( <div> <input onBlur={this.showData} type="text" placeholder="失焦提示数据" /> </div> ); } }
高阶函数
在标签上绑定事件,并希望传入参数时,需要使用高阶函数 或直接在标签中传入回调函数,回调函数中再返回函数
方法 1:
<button onClick={()=>this.handleDelete(id)}>删除</button>
函数返回函数的形式
生命周期
旧版本 17.x 以前
constructor class 类自身的构造器
componentWillMount 组件将要挂载之前
render 挂载组件(初始化渲染 或 更新渲染 时调用)
componentDidMount 组件
挂载完毕
- 一般在这个钩子中做一些初始化的事,例如:开启定时器、发送网络请求、订阅消息
componentWillUnmount 组件将要卸载
- 一般在这个钩子中做一些收尾的事,例如:关闭定时器、取消订阅消息
componentWillReceiveProps 组件将要接受新的数据时( 写在子组件 )
- 坑:第一次页面渲染时不会触发该 生命周期钩子
- 可以接受一个参数 props,即传递的数据形成的对象
forceUpdate() 强制更新
- 当不更改任何状态中的数据时,更新一次界面
卸载组件: 由
ReactDOM.unmountComponentAtNode()
触发- 必须先引入 import ReacrDOM from 'react-dom'
setState()
或父组件触发render()
状态更新阶段- shouldComponentUpdate 控制状态更新的“阀门”
- 默认不写该钩子,返回值一直为 true,允许状态更新
- 返回值为 false 时,阻止状态更新
- componentWillUpdate 组件将要更新
- componentDidUpdate 组件更新完毕
- 可接收三个参数
- 参数 1:之前的 props 值
- 参数 2:之前的 state 值
- 参数 3:接收
getSnapshotBeforeUpdate
的返回值
- shouldComponentUpdate 控制状态更新的“阀门”
//构造器
constructor(props){
console.log('Count---constructor');
super(props)
//初始化状态
this.state = {count:0}
}
//加1按钮的回调
add = ()=>{
//获取原状态
const {count} = this.state
//更新状态
this.setState({count:count+1})
}
//卸载组件按钮的回调
death = ()=>{
ReactDOM.unmountComponentAtNode(document.getElementById('test'))
}
//强制更新按钮的回调
force = ()=>{
this.forceUpdate()
}
//若state的值在任何时候都取决于props,那么可以使用getDerivedStateFromProps
static getDerivedStateFromProps(props,state){
console.log('getDerivedStateFromProps',props,state);
return null
}
//在更新之前获取快照
getSnapshotBeforeUpdate(){
console.log('getSnapshotBeforeUpdate');
return 'atguigu'
}
//组件挂载完毕的钩子
componentDidMount(){
console.log('Count---componentDidMount');
}
//组件将要卸载的钩子
componentWillUnmount(){
console.log('Count---componentWillUnmount');
}
//控制组件更新的“阀门”
shouldComponentUpdate(nextProps,nextState){
console.log(this.props,this.state); // 当前的props和state
console.log(nextProps,nextState); // 接下来要变化的目标props和目标state
// return !this.state.xxx===nextState //可根据值得变化控制是否掉 render函数
return true
}
//组件更新完毕的钩子
componentDidUpdate(preProps,preState,snapshotValue){
console.log('Count---componentDidUpdate',preProps,preState,snapshotValue);
}
新旧版本区别
- 不推荐,但没删除的生命周期钩子
componentWillMount
componentWillReceiveProps
componentWillUpdate
- 这些生命周期方法经常被误解和滥用;17+版本使用时添加 “UNSAFE_” 前缀。( “unsafe” 表示在 React 的未来版本中可能出现 bug,尤其是在启用异步渲染之后)
- 新增两个生命周期钩子(不常用)
- getDerivedStateFromProps
- getSnapshotBeforeUpdate
新增钩子
static getDerivedStateFromProps(props, state)
- 在调用 render 方法之前调用,并且在初始挂载及后续更新时都会被调用
- 必须是静态函数,加 static
- 返回一个对象来更新 state,如果返回
null
则不更新任何内容 - 该钩子会导致代码冗余,并使组件难以维护
jsx// 当 state的值在任何时候都取决于 props 时适用 static getDerivedStateFromProps(props, state){ // props 是标签传递的数据,state是状态数据 return props }
getSnapshotBeforeUpdate
在最近一次渲染输出(提交到 DOM 节点)之前调用。它使得组件能在发生更改之前从 DOM 中捕获一些信息(例如,滚动位置)。此生命周期方法的任何返回值将作为参数传递给
componentDidUpdate()
- 必须有返回值,除了 undefined,可以是任何类型
- 此用法并不常见,但它可能出现在 UI 处理中,如需要以特殊方式处理滚动位置的聊天线程等。
jsx// 需求:新闻条数不断增加,但不会向下滚动 class NewsList extends React.Component { state = { newsArr: [] }; componentDidMount() { setInterval(() => { //获取原状态 const { newsArr } = this.state; //模拟一条新闻 const news = "新闻" + (newsArr.length + 1); //更新状态 this.setState({ newsArr: [news, ...newsArr] }); }, 1000); } getSnapshotBeforeUpdate() { return this.refs.list.scrollHeight; } componentDidUpdate(preProps, preState, height) { this.refs.list.scrollTop += this.refs.list.scrollHeight - height; } render() { return ( <div className="list" ref="list"> {this.state.newsArr.map((n, index) => { return ( <div key={index} className="news"> {n} </div> ); })} </div> ); } } ReactDOM.render(<NewsList />, document.getElementById("test"));
Diffing 算法
- 比较切换的最小粒度:标签
- key 值的作用,与 Vue 中 key 一致
进阶学习
脚手架使用
特点:
- 包含所有需要的配置(语法检查、jsx 编译、devServer...)
- 默认下载好了所有相关依赖
- 整体技术架构:react+webpack+es6+eslint
- 实现:模块化、组件化、工程化
安装脚手架库
- 全局安装:
npm install create-react-app -g
- 创建项目:
create-react-app 项目名
- 进入项目文件下
- 启动项目:
npm start
- 全局安装:
常见命令
shellyarn start #运行项目 yarn build #打包项目 yarn eject #暴露webpack的命令,不可逆的;默认是隐藏的
脚手架项目结构
- public 文件夹
- index.html 唯一 html 页面
- favicon.ico 页签图标
- manifest.json app 加壳的部分配置
- robots.txt 爬虫规则文件
- src
- App.css App 样式文件
- App.test.js App 测试文件,不常用
- App.js App 组件
- index.js 入口文件
- index.css 公共样式文件
- setupTests.js 用于做组件测试。。。
- reportWebVitals.js 记录性能分析的文件。。。
- public 文件夹
其他细节
- 引入 js 或 jsx 文件时,可不写后缀
<!-- index.html 单页面 -->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<!-- %PUBLIC_URL%代表public文件夹的路径 -->
<link rel="icon" href="%PUBLIC_URL%/favicon.ico" />
<!-- 开启理想视口,用于做移动端网页的适配 -->
<meta name="viewport" content="width=device-width, initial-scale=1" />
<!-- 用于配置浏览器页签+地址栏的颜色(仅支持安卓手机浏览器) -->
<meta name="theme-color" content="red" />
<meta
name="description"
content="Web site created using create-react-app"
/>
<!-- 用于指定网页添加到手机主屏幕后的图标 -->
<link rel="apple-touch-icon" href="%PUBLIC_URL%/logo192.png" />
<!-- 应用加壳时的配置文件 -->
<link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
<title>React App</title>
</head>
<body>
<!-- 若浏览器不支持js则展示标签中的内容 -->
<noscript>You need to enable JavaScript to run this app.</noscript>
<div id="root"></div>
</body>
</html>
import React from "react";
import ReactDOM from "react-dom/client";
import "./index.css";
// 引入App组件
import App from "./App";
// 引入记录性能分析的文件 。。。
import reportWebVitals from "./reportWebVitals";
const root = ReactDOM.createRoot(document.getElementById("root"));
// <React.StrictMode>标签包裹,可以用于检查代码中不合理的地方
root.render(
<React.StrictMode>
<App />
</React.StrictMode>
);
// 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();
组件编码流程
- 拆分组件: 拆分界面,抽取组件
- 实现静态组件: 使用组件实现静态页面效果
- 实现动态组件
- 动态显示初始化数据
- 数据类型
- 数据名称
- 保存在哪个组件?
- 交互(从绑定事件监听开始)
样式的模块化
- 将 css 文件名改为 xxx.module.css
- 引入时,采用
import 命名 from 'index.module.css'
- 使用时,
{ 命名.类名 }
// 普通直接引入
import "./index.css";
// 模块化方式引入
// 1.将css文件名改为 xxx.module.css
// 2.引入时,采用 import hello from 'index.module.css'
// 3.使用时,{ hello.类名 }
组件间通讯
父组件 -- 》 子组件
props
父组件中,在子组件标签上
命名={ 数据 }
批量传递数据
jsx<List {...this.state} /> // 传递所有state的数据
子组件中,通过
this.props
获取数据
子组件 --》 父组件
- 自定义事件( 状态在哪,操作数据的方法就在哪 )
- 父组件中定义事件,并传递给子组件
- 子组件中,通过
this.props
得到事件并在合适时机调用,传递数据 - 触发父组件中的自定义事件,拿到数据
- 自定义事件( 状态在哪,操作数据的方法就在哪 )
任意组件间传值
- 消息订阅与发布插件: pub-sub.js、event 等
- 集中式管理 : redux、dva 等
- conText: 生产者-消费者模式(开发用的少,封装插件用的多)
总结:
js// 三种组件间关系 四种通信方式 1.props: (1).children props (2).render props 2.消息订阅-发布: pubs-sub、event等等 3.集中式管理: redux、dva等等 4.conText: 生产者-消费者模式 // 推荐的搭配组合 父子组件:props 兄弟组件:消息订阅-发布、集中式管理 祖孙组件(跨级组件):消息订阅-发布、集中式管理、conText(开发用的少,封装插件用的多)
奇葩的父子组件间传值(插槽,可用于封装组件)
ajax 请求
脚手架配置代理
方法 1
优点:配置简单,前端请求资源时可以不加任何前缀。
缺点:不能配置多个代理。
工作方式:上述方式配置代理,当请求了 3000 不存在的资源时,那么该请求会转发给 5000 (优先匹配前端资源)
json// 在package.json中追加如下配置 "proxy":"http://localhost:5000"
方法 2
第一步:创建代理配置文件
js// 在src下创建配置文件:src/setupProxy.js
编写 setupProxy.js 配置具体代理规则:
jsconst proxy = require("http-proxy-middleware"); module.exports = function (app) { app.use( proxy( "/api", // 转发请求的前缀,遇到就转发代理 { target: "http://localhost:3000/", // 转发地址 pathRewrite: { "^/api": "", //去除该前缀,保证交给后台服务器的是正常请求地址(必须配置) }, // changeOrigin 默认值为false 是真实的host地址,可能会存在问题 changeOrigin: true, // 控制服务器收到的请求头的host(端口)值,用假值 secure: false, // 是否验证证书 ws: true, // 启用websocket } ) ); app.use( proxy("/edge", { target: "https://cpgxpt.zhengqiyezhi666.com:13001", changeOrigin: true, secure: false, // 是否验证证书 ws: true, // 启用websocket }) ); };
说明:
- 优点:可以配置多个代理,可以灵活的控制请求是否走代理。
- 缺点:配置繁琐,前端请求资源时必须加前缀。
路由 5.x
安装命令:
npm i react-router-dom@5
- 2021.11 升级默认版本为 6,因此需要手动指定 5 版本
理解:
- 路由器 router 路由 route
- react 的一个插件库,专门用于实现 SPA 应用
潜在规则:
- 路由组件 放在 page 文件夹下
- 一般组件 放在 components 文件夹下
路由组件 与 一般组件 区别
使用:
在 index.js 中引入 BrowserRouter ,包裹在
<App>
标签外jsx// 一个项目,只能由一个路由进行管理,通过 react-router-dom 分别暴露引入 // 分为两种模式: 哈希 HashRouter 非哈希 BrowserRouter // 不管是 HashRouter,还是 BrowserRouter,底层都是 Router 组件。 // 使用Router的history配置 设置路由模式: // 1. const createHistory = require("history").createBrowserHistory // 2. export default createHistory(); // 3. <Router history={history}> // 更便捷的: import { BrowserRouter, Router } from "react-router-dom"; ReactDOM.render( // <Router> <BrowserRouter> <App /> </BrowserRouter>, // </Router>, document.getElementById("root") );
在页面中放置路由链接,切换组件
<Link/>
普通切换路由<NavLink activeClassName='yangshi'/>
点击效果切换路由,点击谁就给谁 增加类名,默认为 active,也可以通过activeClassName
自己指定- 可以指定样式名
- 标签体内容是一个特殊的标签属性
- 通过 this.props.children 可以获取标签体内容
jsx// 引入 Link标签,在 react-router-dom 上 import {Link,Route} from 'react-router-dom'; // 引入路由组件 import About from '../About/About' // 使用link标签切换路由 ,编写路由链接 // to为要跳转的路径 <Link to='/about'>About</Link> <NavLink activeClassName='yangshi' to='/about'/> // 注册路由,显示的位置 // path 指定当前组件显示的路径, component 指定显示的路由组件 <Route path='/about' component='{About}'/>
封装<NavLink>
- 标签体内容也是 props 的属性 children:'标签体内容'
- 在标签上设置 children:'标签体内容' ;标签体内容会自动变成:标签体内容
import React, { Component } from "react";
import { NavLink } from "react-router-dom";
export default class MyNavLink extends Component {
render() {
// console.log(this.props);
return (
<NavLink
activeClassName="atguigu"
className="list-group-item"
{...this.props}
/>
);
}
}
Switch 标签
当 同一路径注册多个路由,又指向不同的组件时
<Router to='/about'/>
- 默认路径匹配时:全部显示在界面上
- 使用 Switch 标签包裹,可以阻止后续的再匹配,只显示第一个匹配的组件
jsx// 引入Switch import { Switch, Route } from "react-router-dom"; <Switch> <Route path="/about" component="{About}" /> <Route path="/about" component="{About}" /> </Switch>;
Redirect 标签
当所有都不匹配时,指定显示的路由
jsx// 引入Redirect import { Switch, Route, Redirect } from "react-router-dom"; <Redirect to="/about" />;
解决多级路由样式丢失
- 当使用多级路由,再刷新页面时可能出问题
模糊匹配与严格匹配
模糊匹配(默认)
路由链接中写多级路由
注册路由(挖的坑),只要从前往后有匹配的就算匹配上
例: to='/home/a/b' path='/home' 可以匹配
- to='/home' path='/home/a/b' 不能匹配
严格匹配(非必要不要开启严格匹配)
必须完全一致时才算匹配,默认是模糊匹配
exact 属性开启严格匹配
jsx<Route exact path="/home" component={Home} />
注意:
嵌套路由
路由传参
params
暴露在地址栏
- jsx
// 1.路由链接(携带参数) <Link to={`/home/one/${age}/${name}`}>跳转</Link> // 2.注册路由时,声明接收 <Route path='/home/one/:age/:name' component={Test}/> // 3.使用参数 在类的props身上 const {name,age}=this.props.match.params
search
暴露在地址栏
- jsx
// 1.路由链接(携带参数) <Link to={`/home/one/?age=${age}&name=${name}`}>跳转</Link>; // 2.无需在注册路由时声明接收 // 3.接收使用search参数 ?age=18&name=value // 手动切割转化使用,或借助 qs(query-String) 插件的 // qs.parse()方法 将URL解析成对象的形式 // qs.stringify()将对象序列化成URL的形式,以&进行拼接 const { search } = this.props.location;
state 参数
不在地址栏显示,与组件的 state 无关
- jsx
// 1.路由传参, to传递一个对象 // 外层花括号表示是js表达式,内层是个对象 // pathname:'路由路径' state:{key:value,...} <Link to={{pathname:'路由路径' state:{name:'value',age:18}}}>跳转</Link> // 2.接收使用参数 this.props.location.state // 当清除页面缓存后数据丢失,为避免报错,使用 || {} const {name,age} = this.props.location.state || {}
push 和 replace 模式
push 留下痕迹,是默认值
replace 替换,不保留当前界面
- jsx
<Link replace to={"/home/one"}> 跳转 </Link>
编程式路由导航
this.props.history.push('path 路径')
携带参数方法与上面一致
一般组件使用路由
上述所有路由相关 API 均属于路由组件独有,一般组件不具有
如果希望在一般组件身上使用路由,需要引入使用 withRouter 函数,进行组件包装
- withRouter 返回一个新组件
- withRouter 加工一般组件,使其具备路由组件所特有的 API
- jsx
// 引入 withRouter函数 import {withRouter} from "react-router-dom" // 定义一般组件 class Header extends Component {...} // 包装一般组件并导出 export default withRouter(Header)
lazy
路由组件的懒加载
//1.通过React的lazy函数配合import()函数动态加载路由组件 ===> 路由组件代码会被分开打包
// 注:fallback中的组件不能采用懒加载方式引入,必须使用原始引入方式
import Loading from "./Loading";
const Login = lazy(() => import("@/pages/Login"))(
//2.通过<Suspense>指定在加载得到路由打包文件前显示一个自定义loading组件
<Suspense fallback={<Loading />}>
<Switch>
<Route path="/xxx" component={Xxxx} />
<Redirect to="/login" />
</Switch>
</Suspense>
);
组件优化
Component 的 2 个问题
只要执行 setState(),即使不改变状态数据, 组件也会重新 render() ==> 效率低
只要当前组件重新 render(), 就会自动重新 render 子组件,纵使子组件没有用到父组件的任何数据 ==> 效率低
原因:Component 中的 shouldComponentUpdate() 生命周期钩子总是返回 true
解决:
js// 办法1: 借助shouldComponentUpdate()生命周期钩子 比较新旧state或props数据, 如果有变化才返回true, 如果没有返回false //控制组件更新的“阀门” shouldComponentUpdate(nextProps,nextState){ console.log(this.props,this.state); // 当前的props和state console.log(nextProps,nextState); // 接下来要变化的目标props和目标state return !this.state.xxx===nextState // 可根据值得变化控制是否掉 render函数 } // 办法2: 使用PureComponent PureComponent重写了shouldComponentUpdate(), 只有state或props数据有变化才返回true // 注意: 只是进行state和props数据的浅比较, 如果只是数据对象内部数据变了, 返回false 因此不要直接修改state数据, 而是要产生新数据 // 项目中一般使用PureComponent来优化 // 1.引入 PureComponent import React,{PureComponent} from 'react' // 2.使用PureComponent创建组件 export default class Count extends PureComponent { xxx... }
错误边界
错误边界(Error boundary):用来捕获后代组件错误,渲染出备用页面
特点:
只能捕获后代组件生命周期产生的错误,不能捕获自己组件产生的错误和其他组件在合成事件、定时器中产生的错误
使用方式:
js// getDerivedStateFromError 配合 componentDidCatch state={hasError:""} // 生命周期函数,一旦后代组件报错,就会触发 static getDerivedStateFromError(error) { console.log(error); // 在render之前触发 // 返回新的state return { hasError: true, }; } componentDidCatch(error, info) { // 统计页面的错误。发送请求发送到后台去 console.log(error, info); }
render props
向组件内部动态传入带有内容的结构(标签/组件)
Vue中:
使用slot技术, 也就是通过组件标签体传入结构 <A><B/></A>
React中:
使用children props: 通过组件标签体传入结构
使用render props: 通过组件标签属性传入结构,而且可以携带数据,一般用render函数属性
children props
(<A>
<B>xxxx</B>
</A>)
{this.props.children}
// 问题: 如果B组件需要A组件内的数据, ==> 做不到
render props
(<A render={(data) => <C data={data}></C>}></A>)
A组件: {this.props.render(内部state数据)}
C组件: 读取A组件传入的数据显示 {this.props.data}
周边库
react-cookies
可设置失效时间。如果在浏览器端生成 Cookie,默认是关闭浏览器后失效 大小 4K 左右 每次都会携带在 HTTP 头中,如果使用 cookie 保存过多数据会带来性能问题 存在 XSS 注入的风险,只要打开控制台,就可以随意修改它们的值
// 下载依赖
cnpm install react-cookies --save-dev
// 引入
import cookie from 'react-cookies'
cookie.save('userId', "123"); // 存
cookie.load('userId') // 取
cookie.remove('userId') // 删
// 设置失效时间
let inFifteenMinutes = new Date(new Date().getTime() + 24 * 3600 * 1000);//一天
cookie.save('userId', "123",{ expires: inFifteenMinutes });
// 补充:
名字相同cookie是可以同时存在的,cookie不仅有名字和值两个属性,还有域(domain)、路径(path)等属性,不同的域、不同的路径下可以存在同样名字的cookie。
Ant Design
// 1.安装
yarn add antd
// 2.看文档使用即可
状态管理
Redux
// API汇总
store.getState() // 获取store管理的数据
store.dispatch({type:'increment',data:100}) // 触发组件的reducer事件,传入action对象 修改数据
store.subscribe(()=>{...}) // 检测redux中状态的变化,只要数据变化,就触发回调
安装 redux
yarn add redux
src 文件夹下创建 redux 文件夹,统一管理
使用说明
整个应用只有一个
store.js
该文件专门用于暴露 一个 store 对象
// 引入 createStore 专门用于创建redux中最为核心的store对象 import {createStore} from 'redux' // 引人为组件count 服务的reducer import countReducer from './count_reducer.js' // 生成store并暴露 export default createStore(countReducer)
每个组件有自己的
count_reducer.js
该文件用于创建一个为 Count 组件服务的 reducer
- jsx
// reducer本质就是一个函数,并接受两个参数 // 参数1:之前的状态preState // 参数2:动作对象action {type:'things type',data:'things data'} // 初始化时默认 preState为undefined action:{data为空,type为@@redux/INIT+随机字符串} export default function countReducer(preState = 0, action) { // 获取参数数据,执行操作,并return返回结果 const { type, data } = action; switch (type) { case "increment": return preState + data; case "decrement": return preState - data; default: return preState; } }
组件自身
// 引入store import store from '@/redux/store' // 获取store管理的数据 store.getState() // 触发组件的reducer事件,传入action对象 修改数据 store.dispatch({type:'increment',data:100})
action
避免手动去指定加减或其他操作,直接调用对应方法传入数据即可
在 redux 文件夹下 创建 count_action.js
该文件专门为 Count 组件生成 action 对象
// 对象式 同步action export default const creatIncrement = data=>({type:'increment',data}) export default const creatDecrement = data=>({type:'decrement',data}) // 简写时,不能简写为,花括号会被认为函数的花括号 const creatIncrement = data=>{type:'increment',data}
在组件自身
// 引入actionCreat对象,用于创建action对象 import {creatIncrement,creatIncrement} from '@/redux/count_action.js' // 组件的事件中 调用dispatch并触发对应action方法 increment =()=>{ const value = this.selectNumber store.dispatch(creatIncrement(value)) }
同步与异步 action
同步 action 为 Object 对象 形式
异步 action 为 Function 函数 形式
必须借助 redux-thunk 中间件
yarn add redux-thunk
使用步骤
store.js 文件
// 在redux中额外引入applyMiddleware import {createStore,applyMiddleware} from "redux" // 引入redux-thunk 用于支持异步actioon import thunk from 'redux-thunk' // 引入为Count 组件服务的reducer import countReducer from './count_reducer' //暴露store export default createStore(countReducer,applyMiddleware(thunk))
crement_action 文件(该文件专门为 Count 组件生成 action 对象)
// 函数式 异步action 返回值为一个函数 export const createIncrementAsync = (data,time)=>{ return (dispatch)=>{ dispatch(creatIncrement(data)) // store.dispatch(creatIncrement(data)) 不需要单独引入store,可直接使用提供的参数 } }
存在的问题
视图不更新(redux 中存在,但 react-redux 中不存在该问题)
jsx/* 存在问题:store.dispatch 修改状态后,不会触发视图更新 解决方法: 方案1: 1.借助生命周期钩子,在组件挂载完毕componentDidMount后监视store变化 2.使用this.steState({}) 会触发视图更新 方案2:在index.js入口文件中进行全局监视 */ componentDidMount(){ // 检测redux中状态的变化,只要数据变化,就调用render store.subscribe(()=>{ this.setState({}) }) } // 在index.js入口文件中引入 store // 引入store import store from 'src/redux/store.js' // 检测redux中状态的变化,只要数据变化,就为App组件调用render,更新界面 // 因为存在DOM diff算法 不会引起大面积的性能问题 store.subscribe(()=>{ React.render(<App>,document.getElementById('root')) })
react-redux
官方的 redux,react 自己的 新建 redux 文件夹
// 1.安装react-redux
yarn add react-redux
// 引入 ui组件 引入connect 用于连接UI组件与redux
import CountUI from './countUI.js'
import {connect} from 'react-redux'
// 2.创建并暴露容器组件
// 箭头函数默认返回一个对象,需要包一个小括号
// mapStateToProps 简写
state=>({nmb: state.a}),
// mapDispatchToProps 传统写法(函数形式)
// dispatch=>({
// jia:number=> dispatch({type:'jia',data:number}),
// jian:number=> dispatch({type:'jian',data:number}),
// })
// 简写 react-redux 内部实现自动分发dispatch 对象形式
{
jia:{type:'jia',data:number},
jian:{type:'jian',data:number}
}
)(CountUI)
// 在组件中 使用store 并通过 props组件配置形式传入
import store from './store.js'
<Count store={store}/>
- connect
- 第一个括号中传入两个参数
- 第一个参数为函数且返回值作为 状态 传递给 UI 组件
- 参数 1 state:redux 中的 state
- 第二个参数为函数且返回值作为 操作状态的方法 传递给 UI 组件
- 参数:dispatch 方法,直接调用
- 第一个参数为函数且返回值作为 状态 传递给 UI 组件
- 第二个括号中,传入 UI 组件
- 第一个括号中传入两个参数
- 注意:
- 容器组件中的 store 是靠 props 传进去的,而不是在容器组件中直接引入
- 第二个参数 mapDispatchToProps,也可以是一个对象
默认将 store 传递给所有容器组件
- jsx
// 默认情况下,需要为所有的容器组件手动传入 store import store from './store.js' <Count store={store}/> // index.js 入口文件 import React from 'react' import ReactDOM from 'react-dom' import App from './App' import store from './redux/store.js' // 借助 Provider标签自动传递 import {Provider} from 'react-redux' ReactDOM.render( <Provider store={store}> <App/> <Provider/>, documnet,getElementById('root') )
这里差了一些笔记,没有记录!
redux-saga
- 是一个
redux
中间件;一个用于管理应用程序 Side Effect(副作用,例如异步获取数据,访问浏览器缓存等)的 library,它的目标是让副作用管理更容易,执行更高效,测试更简单,在处理故障时更容易。- 处理异步内容
基础使用
// 1.安装
npm install --save redux-saga
Effect 方法
import {take,call,put,select,fork,takeEvery,takeLatest} from 'redux-saga/effects'
// 引入
// take 用来监听action,返回的是监听到的action对象
// 例:可以监听到UI传递到中间件的Action,上述take方法的返回,就是dispatch的原始对象。一旦监听到login动作,返回的action为: {type:'login'}
const loginAction = { type:'login' }
dispatch(loginAction) // UI组件中 dispatch一个action
const action = yield take('login'); // 在saga中使用
// call(apply) 主要用于异步请求,与js中的call和apply相似
// call方法调用fn,参数为args,返回一个描述对象。不过这里call方法传入的函数fn可以是普通函数,也可以是generator。call方法应用很广泛,在redux-saga中使用异步请求等常用call方法来实现。
call(fn, ...args)
yield call(fetch,'/userInfo',username)
// put 对应与redux中的dispatch,可以发出原始action
yield put({type:'login'})
// select 对应的是redux中的getState,用户获取store中的state
const state= yield select()
// fork方法 相当于web work,fork方法不会阻塞主线程,在非阻塞调用中十分有用
// takeEvery和takeLatest
// 用于监听相应的动作并执行相应的方法,是构建在take和fork上面的高阶api,比如要监听login动作
// takeEvery监听到login的动作,就会执行loginFunc方法,除此之外,takeEvery可以同时监听到多个相同的action。
takeEvery('login',loginFunc)
// takeLatest方法跟takeEvery是相同方式调用:
// 与takeEvery不同的是,takeLatest是会监听执行最近的那个被触发的action。
takeLatest('login',loginFunc)
mobx
- https://www.mobxjs.com
- 安装:
yarn add mobx
- 特点
- 利用 getter 和 setter 来收集组建的数据依赖关系,从而在数据发生改变的时候精准重绘(类似 vue)
- 写法上偏向面向对象 oop;可以多 store,并非单一 store
- 不需要使用返回一个新数据,对一份数据直接进行修改
- Redux 默认以原生 js 对象形式存储,而 mobx 使用可观察对象存储
- 两种 react 绑定方式,
- mobx-react-dom:仅支持函数组件
- mobx-react:额外支持基于类式组件
- 实践:
- 可以在任何位置对 数据进行任意的修改(不统一,容易出错,不建议)
- 最佳方式(使用严格模式,根据一定的规范化流程操作数据)
import React, { Component } from "react";
import { observableNumber, autorun } from "mobx";
// 对于普通类型数据的监听
// 定义普通数据 observable.box()
var Numbervalue = observable.box(10);
// 监听,会自动执行一次,之后每次值(autorun中使用到的值)被改变时会再触发 autorun
autorun(() => {
console.log(Numbervalue.get());
});
// 修改数据
Numbervalue.set(20);
// 观察对象,通过map
const myobj = observable.map({ name: "redux", age: 100 });
autorun(() => {
console.log("name属性改变才执行,age改变不执行", myobj.get("name"));
});
myobj.set("name", "mobx");
// 观察对象,不通过map,直接定义直接用
const myobj = observable({ key: "value" });
// 获取:myobj.key 修改:myobj.key='new value'
// 观察数组
const list = observable([1, 2, 4]);
list[2] = 3;
// 开启严格模式,保持代码书写的合规(不允许在store外部随便修改数据)
// 需要配置对应的修改方法,并把他们指定为action
import { observable, configure } from "mobx";
configure({
enforceActions: "always", // 开启严格模式,如果在外部任意修改会报错提示
});
// 定义数据,在专门的store.js中
const store = observable(
{
isShow: true,
changeShow() {
this.isShow = true;
},
changeHide() {
this.isShow = false;
},
},
{
changeShow: action, // 标记这两个方法是action,专门修改可观测的value
changeHide: action,
}
);
export default store;
// 在其他位置使用时
import store from "./xxx/xxx/store.js";
store.changeShow();
// 异步的 使用 runInaction
import { runInaction } from 'mobx'
class Store {
getLIst(){
axios({.....}).then(res=>{
// 在执行getList时,axios会立即返回promise对象,并不会等待then回调,mobx严格模式下会报错
})
}
}
//1.通过React的lazy函数配合import()函数动态加载路由组件 ===> 路由组件代码会被分开打包
// 注:fallback中的组件不能采用懒加载方式引入,必须使用原始引入方式
import Loading from "./Loading";
const Login = lazy(() => import("@/pages/Login"))(
//2.通过<Suspense>指定在加载得到路由打包文件前显示一个自定义loading组件
<Suspense fallback={<Loading />}>
<Switch>
<Route path="/xxx" component={Xxxx} />
<Redirect to="/login" />
</Switch>
</Suspense>
);
// 定义store store/Timer.js
import {makeAutoObservable} from 'mobx'
export class Timer {
secondPassed = 0;
constructor(){
makeAutoObservable(this);
}
increase(){
this.secondsPasses+=1;
}
async decrement(){
const res = await new Promise((resolve)=>{
setTimeout(()=>{
resolve(1)
},500)
})
runInAction(()=>{
this.secondsPasses -= res
})
}
reset(){
this.secondsPasses=0;
}
}
export default new Timer();
// 使用
import {observer} from 'mobx-react-dom'
import Timer from './store/Timer'
functon TimeView(timer){
return (
<div>
<h1>{timer.secondsPasses}</h1>
<button onClick={()=>{ timer.increase }}>add</button>
<button onClick={()=>{ timer.increase }}>add</button>
</div>
)
}
export default observer(TimerView)
// mobx中发送异步请求,使用runInAction包裹异步处理中的数据修改操作
Hooks
(1). Hook 是 React 16.8.0 版本增加的新特性/新语法 (2). 可以让你在函数组件中使用 state 以及其他的 React 特性
state hook
Recoil
facebok 推出新一代 React 状态管理工具
- 只能用在 react 体系框架中 preact react(其他框架体系中无法使用)
- 不能在类组建中使用 Recoil(使用 hooks 实现)
- Recoil 中的状态默认是 Immutable( Object.freeze() 不可更改)
状态管理现状
- redux
- 集中式状态管理(单一数据流)
- 学习成本陡峭(store / state / action / reducer )
- 非官方(redux / redux-thunk / redux-saga / redux-promise )
- recoil
- 分散的状态管理( Atom / Selector )
- 上手快( Hooks 语法 )
- 区别:
- redux 是树形结构的状态,当层级较深时,内部改变需要逐级传递,甚至可能造成不必要的外层节点更新影响性能
- redux
最佳实践
atom (原子状态)
atom 是存储状态的最小单位
副作用:当状态更新时执行的其他逻辑
Immutable 的优势(默认是不可变)
- 降低 Mutable 带来的复杂度,避免被随意修改
- 状态更新时,节省内存空间
- 拥抱函数式编程
atomFamily 允许传参
selector 选择器 (衍生状态 | 计算状态)
- selector 是以其他状态(atom | selector )为参数的纯函数
- selectorFamily 允许传参
- constSelector
- errorSelector
- get 方法,获取原子状态或其他状态
- cachePolicy:选择缓存策略,目前不稳定
jsx// 安装 npm install recoil // 使用 - 初始状态 RecoilRoot - 定义状态 atom | selector - 使用状态 Recoil Hooks // 包裹组件,作为根外层 import React from 'react'; import { RecoilRoot, atom, selector } from 'recoil'; function App() { return ( <RecoilRoot> <CharacterCounter /> </RecoilRoot> ); }
没看的部分
- 123-125 性能优化
- 127+
- 路由:
- 路由的配置
- 动态路由
- React 路由的原理
- Redux-Saga
- D3.js v4.x 周日
- 基本用法、曲线图、柱状图。。。
- git
- Git 教程 - 廖雪峰的官方网站 (liaoxuefeng.com)
- Git 原理入门 - 阮一峰的网络日志 (ruanyifeng.com)
- merge、cherry-pick、reset、checkout、branch...
- github 基本使用
- Linux 基本使用(看 pdf 网站?)
- JS 代码规范(看 pdf 网站?)
- JS 知识
- webpage 教程?
- babel 教程?
- js 设计模式(看 pdf 网站?)
- HTML+CSS 基础教程-慕课网 (imooc.com)
- SVG 图像入门教程 - 阮一峰的网络日志 (ruanyifeng.com)