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表达式
<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文件
既不是字符串也不是 HTML,是一个 JavaScript 的语法扩展;可以很好地描述 UI 应该呈现出它应有交互的本质形式。同时具有 JavaScript 的全部功能。 JavaScript + XML
语法:
- 定义虚拟DOM时,不要写引号
- 标签中混入JS表达式时需要使用
{}
- 样式的类名应使用 className 而不是class,避免与class类的定义混淆
- 内联样式,要用
style={{key:"value"}}
的形式书写- 多个单词的样式,采用驼峰命名法
- 外层花括号表示是一个js表达式,内层括号表示是一个对象
- 最外层只能有一个根标签,所有标签必须闭合
- 标签首字母
- 若小写字母开头,则将该标签转为html中同名元素,若html中无该标签对应的同名元素,则报错!
- 若大写字母开头,react就去渲染对应的组件,若组件没有定义,则报错!
说明:
不能直接被浏览器解析,需要借助
babel.js
将 jsx 转为 jsJSX也是一个表达式,编译后会被转为普通 JavaScript 函数调用,并且对其取值后得到 JavaScript 对象,本质就是一个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(){case:xxx}
- 表达式:一个表达式会产生一个值,可以放在任何需要的地方
使用:
模板中嵌入表达式
- 花括号内可以放置任何有效的JavaScript表达式
const 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中使用这些语句。function getGreeting(user) { if (user) { return <h1>Hello, {formatName(user)}!</h1>; } return <h1>Hello, Stranger.</h1>; }
循环遍历渲染
- 每一项都需要有唯一的标识,key值与vue中的类似
- 不能在jsx中使用 for if 等语句
const 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默认指向组件的实例对象,再结合箭头函数,可简化方法的书写
class 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
使用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 });
//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-types// 引入 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
存在效率上的问题,不建议使用,后期版本可能删除
//创建组件 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 拿到绑定的标签
//创建组件 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
class 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
则不更新任何内容 - 该钩子会导致代码冗余,并使组件难以维护
// 当 state的值在任何时候都取决于 props 时适用 static getDerivedStateFromProps(props, state){ // props 是标签传递的数据,state是状态数据 return props }
getSnapshotBeforeUpdate
在最近一次渲染输出(提交到 DOM 节点)之前调用。它使得组件能在发生更改之前从 DOM 中捕获一些信息(例如,滚动位置)。此生命周期方法的任何返回值将作为参数传递给
componentDidUpdate()
- 必须有返回值,除了undefined,可以是任何类型
- 此用法并不常见,但它可能出现在 UI 处理中,如需要以特殊方式处理滚动位置的聊天线程等。
// 需求:新闻条数不断增加,但不会向下滚动 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
- 全局安装:
常见命令
yarn 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
父组件中,在子组件标签上
命名={ 数据 }
批量传递数据
<List {...this.state} /> // 传递所有state的数据
子组件中,通过
this.props
获取数据
子组件 --》 父组件
- 自定义事件( 状态在哪,操作数据的方法就在哪 )
- 父组件中定义事件,并传递给子组件
- 子组件中,通过
this.props
得到事件并在合适时机调用,传递数据 - 触发父组件中的自定义事件,拿到数据
- 自定义事件( 状态在哪,操作数据的方法就在哪 )
任意组件间传值
- 消息订阅与发布插件: pub-sub.js、event等
- 集中式管理 : redux、dva等
- conText: 生产者-消费者模式(开发用的少,封装插件用的多)
总结:
// 三种组件间关系 四种通信方式 1.props: (1).children props (2).render props 2.消息订阅-发布: pubs-sub、event等等 3.集中式管理: redux、dva等等 4.conText: 生产者-消费者模式 // 推荐的搭配组合 父子组件:props 兄弟组件:消息订阅-发布、集中式管理 祖孙组件(跨级组件):消息订阅-发布、集中式管理、conText(开发用的少,封装插件用的多)
奇葩的父子组件间传值(插槽,可用于封装组件)
ajax请求
脚手架配置代理
方法1
优点:配置简单,前端请求资源时可以不加任何前缀。
缺点:不能配置多个代理。
工作方式:上述方式配置代理,当请求了3000不存在的资源时,那么该请求会转发给5000 (优先匹配前端资源)
// 在package.json中追加如下配置 "proxy":"http://localhost:5000"
方法2
第一步:创建代理配置文件
// 在src下创建配置文件:src/setupProxy.js
编写setupProxy.js配置具体代理规则:
const 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>
标签外// 一个项目,只能由一个路由进行管理,通过 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 可以获取标签体内容
// 引入 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标签包裹,可以阻止后续的再匹配,只显示第一个匹配的组件
// 引入Switch import {Switch,Route} from 'react-router-dom'; <Switch> <Route path='/about' component='{About}'/> <Route path='/about' component='{About}'/> </Switch>
Redirect标签
当所有都不匹配时,指定显示的路由
// 引入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 属性开启严格匹配
<Route exact path='/home' component={Home}/>
注意:
嵌套路由
路由传参
params
暴露在地址栏
// 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
暴露在地址栏
// 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无关
// 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 替换,不保留当前界面
<Link replace to={'/home/one'}>跳转</Link>
编程式路由导航
this.props.history.push('path路径')
携带参数方法与上面一致
一般组件使用路由
上述所有路由相关API均属于路由组件独有,一般组件不具有
如果希望在一般组件身上使用路由,需要引入使用 withRouter函数,进行组件包装
- withRouter 返回一个新组件
- withRouter加工一般组件,使其具备路由组件所特有的API
// 引入 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
解决:
// 办法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):用来捕获后代组件错误,渲染出备用页面
特点:
只能捕获后代组件生命周期产生的错误,不能捕获自己组件产生的错误和其他组件在合成事件、定时器中产生的错误
使用方式:
// 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
{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
// 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中不存在该问题)
/* 存在问题: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传递给所有容器组件
// 默认情况下,需要为所有的容器组件手动传入 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:选择缓存策略,目前不稳定
// 安装 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)