0
点赞
收藏
分享

微信扫一扫

【react】react学习笔记

booksmg2014 2022-02-10 阅读 80

目录

1.hello react

2.jsx语法规则

3.组件

3.1 创建函数式组件

3.2 类式组件的创建 

3.2.1 类的基本知识

3.2.2 类式组件

3.2.3 组件实例的三大核心属性

 4. 事件处理

5.受控组件

6.非受控组件

6.1对上述代码的优化

 7. 组件的生命周期

7.1生命周期(旧)

7.1.1组件初始化流程

7.1.2组件更新流程

7.1.3组件强制更新 

 7.1.4卸载组件

 7.1.5 父组件render流程

 7.2生命周期(新)

 8.React脚手架

8.1创建项目并启动

 8.2react脚手架项目结构

8.3 使用react脚手架创建一个hello world项目

8.4 样式的模块化

 8.5 react脚手架中配置代理

8.5.1在package.json中配置

8.5.2 创建代理配置文件

 9 fetch

10 消息订阅与发布

11. react路由 

11.1 路由的基本使用

 11.2 路由组件和一般组件

11.3 NavLink的使用

11.4 switch标签

11.5 解决多级路径页面刷新样式丢失

11.6  严格匹配

11.7  redirect的使用

11.8 嵌套路由

11.9 路由组件传参

11.9.1 向路由组件传递params参数

11.9.2 向路由组件传递search参数

11.9.3 向路由组件传递state参数

11.10 编程式路由导航

11.11 withRouter 

 11.12 BrowserRouter和HashRouter的对比

12. redux

12.1 redux的基本原理

12.2 求和案例

12.3 异步action

12.4 react-redux

12.4.1 connect 参数优化

12.4.2 Provider组件

12.4 .3 使用redux实现多组件通信

 12.5 纯函数

12.6 redux开发者工具

13 lazyLoad 懒加载

14 hooks

15.1.state hook

15.2 Effect hook

15.3 Ref hook

16 Context

17. 组件优化

18. renderProps

19.错误边界


1.hello react

1.react需要使用三个依赖,必须按照先后顺序依次引入

2.resct使用的是jsx语言,再script标签的type属性上要写明“text/babel”

<body>
<!-- 创建容器 -->
<div id="app"></div>
<!-- 引入react核心库,全局就有了React对象 -->
<script src="./js/react.development.js"></script>
<!-- 引入react-dom,支持react的dom操作,全局就有了ReactDOM -->
<script src="./js/react-dom.development.js"></script>
<!-- 引入babel,将jsx转换为js -->
<script src="./js/babel.min.js"></script>
<script type="text/babel">
// 创建虚拟dom,此处不要加引号
const VDOM = <h1>hello react</h1>;
// 渲染虚拟dom到页面
ReactDOM.render(VDOM,document.getElementById("app"));
</script>
</body>

react虚拟dom的另一种创建方式:但是这种方式对于层级较深的html结构是非常复杂的,一般使用方法一,且方法一在运行的时候也是转换成方法二的。

<body>
<!-- 创建容器 -->
<div id="app"></div>
<script type="text/javascript">
const VDOM = React.createElement('h1',(id:'title'),'hello react');
ReactDOM.render(VDOM,document.getElementById('test'));
</script>
</body>

2.jsx语法规则

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <title>Document</title>
    <style>
        .title{
            background-color: coral;
        }
        #abcdef{
            border: 1px solid black;
        }
    </style>
</head>
<body>
    <div id="test"></div>
    <script src="./js/react.development.js"></script>
    <script src="./js/react-dom.development.js"></script>
    <script src="./js/babel.min.js"></script>
    <script type="text/babel">
        const myId = 'abCdeF';
        const myData = 'hello,rEaCt';
		// 多行html虚拟dom写法---外面要加括号
        const VDOM = (
            <div>
            	<!--js表达式要用{}包裹 -->
                <h2 className="title" id={myId.toLowerCase()}>
                    <span style={{color:'white',fontSize:'29px'}}>{myData.toLowerCase()}</span>
                </h2>
				<!-- 必须要有结束标记或标签-->
                <input type="text"/>
            </div>
        );
        ReactDOM.render(VDOM,document.getElementById("test"));
    </script>
</body>

3.组件

3.1 创建函数式组件

执行过程:

1.React解析组件标签,找到MyComponent组件;

2.发现组件是使用函数定义的,随后调用该函数,将返回的虚拟dom转换为真实dom,随后呈现在页面中;

<body>
<div id="test"></div>
<script src="./js/react.development.js"></script>
<script src="./js/react-dom.development.js"></script>
<script src="./js/babel.min.js"></script>
<script type="text/babel">
// 创建函数式组件
function MyComponent(){
console.log(this); // undefined
return <h1>这是一个自定义react组件</h1>;
}
ReactDOM.render(<MyComponent/>,document.getElementById("test"));
</script>
</body>

在函数组件中的this是undefined,因为babel编译后开启了严格模式

注意点:

3.2 类式组件的创建 

3.2.1 类的基本知识

1.类中的构造器不是必须写的,要对实例进行一些初始化操作,如添加指定属性时候才写;

2.如果A类继承了B类,且A类中写了构造器,那么A类的构造器是必须调用的;

3.类中定义的方法,都是放在类的原型对象上,供实例使用;

<script>
class person{
constructor(name,age){
this.name = name;
this.age = age;
}
speak(){
console.log(this.name,this,age);
}
}

class student extends person{
constructor(name,age,grade){
super(name,age);
this.grade = grade;
}

speak(){
console.log(this.name,this.grade);
}
study(){
console.log("我学习!");
}
}

const s = new student("zs",12,90);
console.log(s);
</script>

3.2.2 类式组件

执行了ReactDOM.render()后:

1.react解析组件标签,找到myComponent组件

2.发现组件是使用类定义的,new出该类的实例,通过该实例调用原型上的render方法;

3.将render返回的虚拟dom转换为真实dom,呈现于页面;
 

<div id="test"></div>
<script src="./js/react.development.js"></script>
<script src="./js/react-dom.development.js"></script>
<script src="./js/babel.min.js"></script>
<script type="text/babel">
class MyComponent extends React.Component {
render(){
return <h1>自定义类组件</h1>;
}
}
ReactDOM.render(<MyComponent/>,document.getElementById('test'));
</script>

render(){}是放在myComponent的原型对象上,供实例使用;

render(){}中的this是myComponent的实例对象。·

3.2.3 组件实例的三大核心属性

3.2.3.1 state属性

点击文字,每次点击切换当前天气的状态,即炎热凉爽之间切换

class Weather extends React.Component {
// 构造器在整个过程中调用一次
constructor(props){
super(props);
// 初始化状态
this.state = {isHot:false};
// 解决changeWeather中this指向问题
this.changeWeather = this.changeWeather.bind(this);
}
// render调用1 + n次,初始化调用一次,以及状态更新时调用
render(){
const {isHot} = this.state;
// this.changeWeather不能加括号,加了括号就是将这个函数的返回值给onClick事件作为回调,不加括号则意味着是将整个函 数作为回调
return <h2 onClick={this.changeWeather}>今天天气很{isHot ? '炎热' : '凉爽'}</h2>
}
// 点几次调用几次
changeWeather(){
// changeWeather放在Weather原型对象上,供实例使用
// changeWeather 是作为onClick的回调,不是通过实例调用的,是直接调用
// 类中的方法默认开启了局部严格模式,所以changWeather中的this是undefined
const {isHot} = this.state;
console.log(isHot);
// 状态不能直接修改,要通过setState进行修改
this.setState({isHot:!isHot});
}
}
ReactDOM.render(<Weather/>,document.getElementById('test'));

 简写形式:

<script type="text/babel">
class Weather extends React.Component {
// 初始化状态
state = {isHot:false};
render(){
const {isHot} = this.state;
return <h2 onClick={this.changeWeather}>今天天气很{isHot ? '炎热' : '凉爽'}</h2>
}
// 自定义函数 赋值语句 + 箭头函数
changeWeather = () => {
const {isHot} = this.state;
this.setState({isHot:!isHot});
}
}
ReactDOM.render(<Weather/>,document.getElementById('test'));
</script>

state是组件对象最重要的属性,值是对象,可以包括多个key-value组合;

组件被称为“状态机”,通过更新组件的state来更新对应页面的显示,重新渲染组件;

组件中render方法中的this为组件实例对象;

自定义方法中this为undefined,可以强制绑定this,通过函数对象的bind();箭头函数。

状态数据不能直接修改或更新

3.2.3.2 props属性

组件标签所有属性都保存在props中;

通过标签属性从组件外向内传递变化的数据;

组件内部不能修改props数据;

标签可以直接写属性,也可以通过构造对象字面量的方式;

可以对传递的参数进行限制,需要进行单独配置(类名.propTypes);其中对于属性的配置在react15.5之前可以使用React.PropTypes,在15.5之后需要引入prop-types.js,相当于在全局中引入了PropTypes对象;

在标签中需要传入数值型的属性时,数值外面要加{};

<div id="test1"></div>
<div id="test2"></div>
<div id="test3"></div>
<script src="./js/react.development.js"></script>
<script src="./js/react-dom.development.js"></script>
<script src="./js/babel.min.js"></script>
<script src="./js/prop-types.js"></script>
<script type="text/babel">
class Person extends React.Component {
render(){
const {name,age,sex} = this.props;
return(
<ul>
<li>姓名:{name}</li>
<li>性别:{sex}</li>
<li>年龄:{age+1}</li>
</ul>
)
}
}
// 对标签属性进行类型,必要性的限制
Person.propTypes = {
name:PropTypes.string.isRequired, //限制name必传,且为字符串
sex:PropTypes.string, // 设置sex为字符串
age:PropTypes.number, // 设置age为数值
speak:PropTypes.func // 设置speak为函数,注意写法func
}

// 标签属性默认值
Person.defaultProps = {
sex:'male',
age:18
}
ReactDOM.render(<Person name="zs" age={18} sex="male" speak={speak}/>,
document.getElementById('test1'));
const p = {name:'ls',age:19,sex:'男'};
// 批量传递props
// 构造对象字面量时展开运算符不能展开对象即(...对象名),但可以{...对象名}
// 在react中babel + react可以展开一个对象,但仅适用于标签属性的传递
// {}包裹在react表达式外面,其实写法是...p
ReactDOM.render(<Person {...p}/>,document.getElementById('test2'));

function speak(){
console.log('speak');
}
</script>

props的简写形式,将propTypes作为类的静态属性

<div id="test1"></div>
<div id="test2"></div>
<div id="test3"></div>
<script src="./js/react.development.js"></script>
<script src="./js/react-dom.development.js"></script>
<script src="./js/babel.min.js"></script>
<script src="./js/prop-types.js"></script>
<script type="text/babel">
class Person extends React.Component {
constructor(props){
// 构造器是否接收props,是否传递给super,取决于是否希望在构造函数中通过this访问props
super(props);
console.log(this.props);
}
render(){
const {name,age,sex} = this.props;
return(
<ul>
<li>姓名:{name}</li>
<li>性别:{sex}</li>
<li>年龄:{age+1}</li>
</ul>

)
}
// 对标签属性进行类型,必要性的限制
static propTypes = {
name:PropTypes.string.isRequired, //限制name必传,且为字符串
sex:PropTypes.string, // 设置sex为字符串
age:PropTypes.number, // 设置age为数值
speak:PropTypes.func
}

// 标签属性默认值
static defaultProps = {
sex:'male',
age:18
}

state = {}
}

ReactDOM.render(<Person name="zs" sex="male" speak={speak}/>,document.getElementById('test1'));
const p = {name:'ls',age:19,sex:'男'};
// 批量传递props
// 构造对象字面量时展开运算符不能展开对象即(...对象名),但可以{...对象名}
// 在react中babel + react可以展开一个对象,但仅适用于标签属性的传递
// {}包裹在react表达式外面,其实写法是...p
ReactDOM.render(<Person {...p}/>,document.getElementById('test2'));

function speak(){
console.log('speak');
}
</script>

3.2.3.3 refs属性

组件内的标签可以通过refs来标识自己,相当于id选择器

1.字符串形式的ref(不推荐使用)

在标签中使用ref打标识ref="标识名",react会将所有收集到的标识放入组件的refs中,以key(标识名):value(标签真实dom)的形式

    <div id="test"></div>
<script src="./js/react.development.js"></script>
<script src="./js/react-dom.development.js"></script>
<script src="./js/babel.min.js"></script>
<script type="text/babel">
class Demo extends React.Component {
showData1 = () => {
const {input1} = this.refs;
alert(input1.value);
}
showData2 = () => {
const {input2} = this.refs;
alert(input2.value);
}
render(){
return(
<div>
<input type="text" ref="input1" placeholder="点击按钮提示数据"/>
<button onClick={this.showData1}>点我提示左侧数据</button>
<input type="text" ref="input2" onBlur={this.showData2} placeholder="失去焦点提示数据"/>
</div>

)
}
}
ReactDOM.render(<Demo/>,document.getElementById('test'));
<script type="text/babel">

ref写成**内联函数**的问题:在页面更新过程中会调用两次,第一次会传入参数c为null,第二次才会传入dom元素。

由于每次渲染时会创建一个新的函数实例,所以react会先清空旧的再传入新的;

可以通过ref的回调定义为**类的绑定函数**的方式避免;(见上述代码24行);但影响不大,实际还是多用内联函数。

 3. creatRef的使用

React.createRef调用后可以返回一个容器,该容器可以存储被ref所标识的节点,该容器是专人专用的

<div id="test"></div>
<script src="./js/react.development.js"></script>
<script src="./js/react-dom.development.js"></script>
<script src="./js/babel.min.js"></script>
<script type="text/babel">
class Demo extends React.Component {
myRef1 = React.createRef();
myRef2 = React.createRef();
showData1 = () => {
console.log(this.myRef1); // {current: input}
alert(this.myRef1.current.value);
}
showData2 = () => {
alert(this.myRef2.current.value);
}
render(){
return(
<div>
<input type="text" ref={this.myRef1} placeholder="点击按钮提示数据"/>
<button onClick={this.showData1}>点我提示左侧数据</button>
<input type="text" ref={this.myRef2} onBlur={this.showData2} placeholder="失去焦点提示数据"/>
</div>

)
}
}
ReactDOM.render(<Demo/>,document.getElementById('test'));
</script>

 4. 事件处理

1.通过onXxx属性指定事件处理函数

     React使用的是自定义事件,而不是dom原生事件 --- 为了更好的兼容性

​     React中的事件是通过事件委托的方式处理的,委托给最外层的元素 --- 为了高效

2.通过event.target得到发生事件的dom元素对象 ,所以不要过度使用ref,当发生事件的元素是要操作的元素时,可以省略ref,在事件的回调中通过event.target获取,代码如下:

<div id="test"></div>
<script src="./js/react.development.js"></script>
<script src="./js/react-dom.development.js"></script>
<script src="./js/babel.min.js"></script>
<script type="text/babel">
class Demo extends React.Component {
showData2 = (event) => {
alert(event.target.value);
}
render(){
return(
<div>
<input type="text" onBlur={this.showData2} placeholder="失去焦点提示数据"/>
</div>

)
}
}
ReactDOM.render(<Demo/>,document.getElementById('test'));
</script>

5.受控组件

页面中的输入类型节点随着输入,将输入内容维护到状态state中去,需要使用的时候从状态state中取出。

能够省略调ref;

类似于vue中的双向数据绑定。
 

<script type="text/babel">
class Form extends React.Component{
// 状态初始化
state = {
username:'',
password:''
}
handleName = (event) => {
// 拿到输入的内容,放入state中保存
this.setState({username:event.target.value});
}
handlePwd = (event) => {
this.setState({password:event.target.value});
}
handleSubmit = (event) => {
event.preventDefault();
// 从state中取出输入的数据
alert(`输入的用户名为${this.state.username},密码为${this.state.password}`)
}
render(){
return (
<form onSubmit={this.handleSubmit}>
姓名:<input type="text" name="username" onChange={this.handleName}/>
密码:<input type="password" name="pwd" onChange={this.handlePwd}/>
<button>提交</button>
</form>
)
}
}
ReactDOM.render(<Form/>,document.getElementById('test'));
</script>

6.非受控组件

页面中的输入节点,将输入的内容直接取出使用。

<script type="text/babel">
class Form extends React.Component{
handleSubmit = (event) => {
event.preventDefault(); // 阻止表单提交
const {input1,input2} = this;
alert(`输入的用户名为${input1.value},密码为${input2.value}`);
}
render(){
return(
<form onSubmit={this.handleSubmit}>
姓名:<input type="text" ref={c => this.input1 = c} name="username"/>
密码:<input type="password" ref={c => this.input2 = c} name="pwd"/>
<button>提交</button>
</form>
)
}
}
ReactDOM.render(<Form/>,document.getElementById('test'));
</script>

6.1对上述代码的优化

1.高阶函数:如果一个函数满足下面两个规范中的任何一个规范,那么该函数就是高阶函数

​    1.1 若A函数,接收的参数是一个函数

​    1.2 若A函数,调用的返回值依然是一个函数

常见的高阶函数有:Promise,setTimeout,arr.map()等

2. 函数的柯里化:通过函数调用继续返回函数的形式,实现多次接收参数最后统一处理的函数编码形式

add(a){
return (b) => {
return (c) => {
return a + b + c;
}
}
}
add(1)(2)(3);

代码优化:

this.handleChange()返回给onChange事件的是该方法的返回值,所以给handleChange()方法的返回一个回调函数,给onChange接收,即当表单数据发生改变的时候,就执行该回调,该回调的参数就是当前表单的事件对象event,通过这种函数柯里化的形式,解决给事件回调传递参数.

<script type="text/babel">
class Form extends React.Component {
state = {
username:'',
password:''
}
handleChange = (type) => {
return (event) => {
this.setState({[type]:event.target.value});
}
}
handleSubmit = () => {
const {username,password} = this.state;
alert(`${username}${password}`);
}
render(){
return (
<form onSubmit={this.handleSubmit}>
姓名:<input type="text" name="username" onChange={this.handleChange('username')}/>
密码:<input type="password" name="pwd" onChange={this.handleChange('password')}/>
<button>提交</button>
</form>
)
}
}
ReactDOM.render(<Form/>,document.getElementById('test'));
</script>

不使用柯里化解决:

直接传递给onChange事件一个回调,在这个回调种返回handleChange函数,将两个参数传入:

<script type="text/babel">
class Form extends React.Component {
state = {
username:'',
password:''
}
handleChange = (type,event) => {
this.setState({[type]:event.target.value});
}
handleSubmit = () => {
const {username,password} = this.state;
alert(`${username}${password}`);
}
render(){
return (
<form onSubmit={this.handleSubmit}>
姓名:<input type="text" onChange={event => this.handleChange('username',event)}/>
密码:<input type="password" onChange={event => this.handleChange('password',event)}/>
<button>提交</button>
</form>
)
}
}
ReactDOM.render(<Form/>,document.getElementById('test'));
</script>

 7. 组件的生命周期

组件从创建到死亡要经历一些特定的阶段;React组件中包含一系列钩子函数,会在特定的时刻调用;我们在定义组件时,会在特定的生命周期函数中,做特定的工作。

7.1生命周期(旧)

7.1.1组件初始化流程

class Count extends React.Component {
// 构造器
constructor(props){
console.log("constructor");
super(props);
this.state = {count:0}
}

// 加1按钮回调
add = () => {
const {count} = this.state;
this.setState({
count:count + 1
})
}

// 组件将要挂载的钩子
componentWillMount(){
console.log("componentWillMount");
}

// 组件挂载完毕的钩子
componentDidMount(){
console.log("componentDidMount")
}
render(){
console.log("render");
const {count} = this.state;
return (
<div>
<h2>当前求和为{count}</h2>
<button onClick={this.add}>点我 + 1</button>
</div>
)
}
}
ReactDOM.render(<Count/>,document.getElementById('test'));

7.1.2组件更新流程

class Count extends React.Component {
// 构造器
constructor(props){
console.log("constructor");
super(props);
this.state = {count:0}
}

// 加1按钮回调
add = () => {
const {count} = this.state;
this.setState({
count:count + 1
})
}

// 控制组件更新的阀门,默认不写react会自动调用并返回true;
// 返回true则允许更新组件,返回false不更新组件即不调用render()
// 如果写了不return,默认return的是undefined(false)
shouldComponentUpdate(){
console.log("shouldComponentUpdate");
return true;
}

// 组件将要更新的钩子
componentWillUpdate(){
console.log("componentWillUpdate");
}

// 组件更新完毕的钩子
componentDidUpdate(){
console.log("componentDidUpdate");
}

render(){
console.log("render");
const {count} = this.state;
return (
<div>
<h2>当前求和为{count}</h2>
<button onClick={this.add}>点我 + 1</button>
</div>
)
}
}
ReactDOM.render(<Count/>,document.getElementById('test'));

点击+1按钮控制台输出如下:

7.1.3组件强制更新 

对状态不做任何修改而更新组件,不受阀门shouldComponentUpdate影响

class Count extends React.Component {
// 构造器
constructor(props){
super(props);
this.state = {count:0}
}

// 加1按钮回调
add = () => {
const {count} = this.state;
this.setState({
count:count + 1
})
}

// 强制更新组件
forceUp = () => {
this.forceUpdate();
}

// 控制组件更新的阀门
shouldComponentUpdate(){
console.log("shouldComponentUpdate");
return true;
}

// 组件将要更新的钩子
componentWillUpdate(){
console.log("componentWillUpdate");
}

// 组件更新完毕的钩子
componentDidUpdate(){
console.log("componentDidUpdate");
}

render(){
console.log("render");
const {count} = this.state;
return (
<div>
<h2>当前求和为{count}</h2>
<button onClick={this.add}>点我 + 1</button>
<button onClick={this.forceUp}>点击强制更新</button>
</div>
)
}
}
ReactDOM.render(<Count/>,document.getElementById('test'));

点击强制更新按钮,控制台输出如下:

 7.1.4卸载组件

class Count extends React.Component {
// 卸载组件
deleted = () => {
ReactDOM.unmountComponentAtNode(document.getElementById("test"));
}

// 组件卸载钩子
componentWillUnmount(){
console.log("componentWillUnmount");
}
render(){
console.log("render");
return (
<div>
<h2>当前求和为8</h2>
<button onClick={this.deleted}>点击卸载</button>
</div>
)
}
}
ReactDOM.render(<Count/>,document.getElementById('test'));

 7.1.5 父组件render流程

A组件为父组件,B组件为子组件,当组件第二次起获取父组件传递的参数时,调用componentWillReceiveProps钩子

class A extends React.Component{
state = {car1:'奔驰'}
changeCar = () => {
this.setState({car1:'宝马'})
}
render(){
return (
<div>
<h2>这是A组件</h2>
<button onClick={this.changeCar}>切换车</button>
<B car={this.state.car1}/>
</div>
)
}
}

class B extends React.Component{
// 组件将要接收函新的props的钩子,第一次传递props的时候不算
componentWillReceiveProps(props){
console.log("componentWillReceiveProps",props);
}

// 控制组件更新的阀门
shouldComponentUpdate(){
console.log("shouldComponentUpdate");
return true;
}

// 组件将要更新的钩子
componentWillUpdate(){
console.log("componentWillUpdate");
}

// 组件更新完毕的钩子
componentDidUpdate(){
console.log("componentDidUpdate");
}

// 更新
render(){
console.log("render");
return (
<h2>这是子组件B,接收到A传递的参数{this.props.car}</h2>
)
}
}
ReactDOM.render(<A/>,document.getElementById('test'));

点击切换按钮,控制台输出如下:

 

 7.2生命周期(新)

class Count extends React.Component {
// 构造器
constructor(props){
console.log("constructor");
super(props);
this.state = {count:0}
}

// 加1按钮回调
add = () => {
const {count} = this.state;
this.setState({
count:count + 1
})
}

// 强制更新组件
forceUp = () => {
this.forceUpdate();
}

// state的值在任何时候都要取决于props的时候使用,这里会将state的值改为return的props,即count:9
// 该钩子必须以static修饰,且返回值必须是null或者props对象
static getDerivedStateFromProps(props,state){
console.log("getDerivedStateFromProps",props,state);
//return props;
return null;
}

// 在组件更新前获取信息,把更新前的部分信息记录下来,比如某个div的高度
// 必须返回null或者快照信息
getSnapshotBeforeUpdate(){
console.log("getSnapshotBeforeUpdate");
return "2021102";
}
// 组件挂载完毕的钩子
componentDidMount(){
console.log("componentDidMount")
}

// 控制组件更新的阀门
shouldComponentUpdate(){
console.log("shouldComponentUpdate");
return true;
}

// 组件更新完毕的钩子
// 可以接收三个参数,更新之前的props,更新之前的状态,更新前getSnapshotBeforeUpdate返回的快照信息
componentDidUpdate(preProps,preState,snapshotValue){
console.log("componentDidUpdate",preProps,preState,snapshotValue);
}

// 组件卸载钩子
componentWillUnmount(){
console.log("componentWillUnmount");
}
render(){
console.log("render");
const {count} = this.state;
return (
<div>
<h2>当前求和为{count}</h2>
<button onClick={this.add}>点我 + 1</button>
<button onClick={this.forceUp}>点击强制更新</button>
<button onClick={this.deleted}>点击卸载</button>
</div>
)
}
}
ReactDOM.render(<Count count="9"/>,document.getElementById('test'));
```

 将26行改为return null,点击点我+1,控制台输出如下:

 8.React脚手架

用来帮助程序员快捷创建一个基于xxx库的模板项目

项目的整体技术架构为:react+webpack+es6+eslint

react提供了一个用于创建react项目的脚手架库create-react-app

8.1创建项目并启动

 8.2react脚手架项目结构

8.3 使用react脚手架创建一个hello world项目

 1. 首先在项目根目录下创建public和src两个文件

2.在public文件下创建index.html,作为项目的主页面;在src目录下创建App.js作为项目的根组件,index.js作为项目的入口js 

3.在index.html中创建主页面的根节点

 4.在入口index.js中分别引入react react-dom App.js,并将App组件渲染到页面

 5.创建外壳组件App.js,使用分别暴露和统一暴露方式引入React和Component,创建App类组件;

    将App组件暴露给外部export default

    外壳组件中的内容一般使用组件的方式编写,这里还需再创建Hello Welcome两个组件

6. 在src目录下新建Component文件,用于存放自定义组件,在其下新建Hello,Welcome两个文件,存放两个组件的内容,组件的文件名的首字母习惯上用大写字母。此外为了区分组件和普通业务功能的js文件,我们习惯上将组件文件的后缀名命名为jsx,以作区分。css文件以css为后缀名。

7.分别创建两个组件,工程同App.js,最后在App.js中引入俩个组件,在render中添加两个组件标签(见第5步)。

8.按ctrl+`打开控制台,切换到项目当前目录,npm start启动项目,可以查看到最终效果。

8.4 样式的模块化

在上述案例中,welcome和Hello两个组件的最终内容都会汇总到App中,从而两个组件样式存在冲突,即出现在两个组件中的同名的类选择器会产生冲突,后引入的组件样式会覆盖先引入的。

1.将需要模块话的css文件名写成:文件名.module.css的格式

2.在组件中的css的import语句中给引入的css模块命名,在响应的类选择器处用css模块名.选择器名的表达式替代

 8.5 react脚手架中配置代理

开发阶段,前端代码请求服务器,若两者端口号等不一致,则受同源策略保护,请求响应时会被拦截。

要在本地新建一个模拟的服务器(端口号相同),代替我们将请求转发给服务器,那么由于服务器之间的请求转发不存在同源策略限制,不会存在跨域问题。

8.5.1在package.json中配置

"proxy":"http://loaclhost:5000"

优点:配置简单,前端请求资源时候可以不添加任何前缀

缺点:不可配置多个代理

工作方式:当请求了3000不存在的资源时,将请求转发给5000

8.5.2 创建代理配置文件

1.创建创建代理配置文件

​    在src目录下创建配置文件:src/setupProxy.js

2.编写setupProxy配置代理具体规则:

 9 fetch

原生函数,不使用XMLHttpRequest对象提交Http请求,Promise风格;

对于老版本的浏览器不支持

fetch(`http://localhost:3000/search/users?q=${input}`).then(
res => {

// 联系服务器成功了。要获取数据得调用json方法,它也是一个Promise
retrun res.json();
},
err => {
// 联系服务器失败了
console.log(err);
// 中断Promise链。由于默认返回值是undefined,依旧会走下一个then的成功回调。
return new Promise(() => {});
}
).then(
res => {
console.log(‘获取数据成功了’,res);
},
err => {
console.log(“获取数据失败了”,err);
}
)

用async/await:
 

async demo1(){
try{
const result = await fetch(`http://localhost:3000/search/users?q=${input}`);
const {items} = await result.json();
Pubsub.publish('getSearchData',{users:newArr,isLoading:false});
} catch(err){
Pubsub.publish("getSearchData",{isLoading:false,isError:true});
}
}

10 消息订阅与发布

工具库:Pubsub.js

解决兄弟组件之间转递参数的问题

下载安装:

使用:

​ 1.订阅消息,写在接收数据的组件里

 2. 发布消息,写在传送数据的组件里

    3. 取消订阅,写在接收数据的组件中的componentWillUnmount钩子中

11. react路由 

11.1 路由的基本使用

1. 明确好界面中的导航区域和展示区域

2. 导航区域的a标签改为Link标签:

3.展示区写Route标签进行路径的匹配

​4.<App>的最外侧包裹一个<BrowserRouter><HashRouter>

import React, { Component } from 'react'
import {Link,Route} from 'react-router-dom'

import About from './Components/About'
import Home from './Components/Home'

export default class App extends Component {
render() {
return (
<div>
<div className="row">
<div className="col-xs-offset-2 col-xs-8">
<div className="page-header"><h2>React Router Demo</h2></div>
</div>
</div>
<div className="row">
<div className="col-xs-2 col-xs-offset-2">
<div className="list-group">
{/* 编写路由链接 */}
<Link className="list-group-item " to="/about">About</Link>
<Link className="list-group-item" to="/home">Home</Link>
</div>
</div>
<div className="col-xs-6">
<div className="panel">
<div className="panel-body">
{/* 注册路由 */}
<Route path="/about" component={About}/>
<Route path="/home" component={Home}/>
</div>
</div>
</div>
</div>
</div>
)
}
}

index.js

// 引入react核心库
import React from 'react'
// 引入reactDOM
import ReactDOM from 'react-dom'
import {BrowserRouter} from 'react-router-dom'
// 引入App组件
import App from './App'

// 渲染App组件到页面
ReactDOM.render(
<BrowserRouter>
<App/>
</BrowserRouter>,
document.getElementById('root')
);

效果:

 11.2 路由组件和一般组件

1. 路由组件一般放在pages文件夹下;一般组件放在components文件夹下。
2. 写法不同:
   1. ·路由组件:<Route path='/demo' component={Demo}>
   2. 一般组件:<Demo/>

3. 接收到的props不同:

   1. 路由组件:能接收到三个固定的属性

      history:

      location:

      match:

   2. 一般组件:组件标签传递什么就接收什么;

11.3 NavLink的使用

能实现点击组件标签呈现高亮效果;

高亮效果的样式通过activeClassName属性指定类选择器

import {NavLink} from 'react-router-dom'
import './App.css'

......

<div className="list-group">
{/* 编写路由链接 */}
<NavLink className="list-group-item " activeClassName="acn" to="/about">About</NavLink>
<NavLink className="list-group-item" activeClassName="acn" to="/home">Home</NavLink>
</div>

 

封装NavLink:

多个标签都要呈现同一种选中效果,避免重复的代码出现 

App.js文件:

import MyNavLink from './Components/MyNavLink'
import './App.css'

<div className="list-group">
{/* 编写路由链接 */}
<MyNavLink to="/about" children="about"/>
<MyNavLink to="/home" children="home"/>
</div>

MyNavLink.jsx文件:

import React, { Component } from 'react'
import {NavLink} from 'react-router-dom'

export default class index extends Component {
render() {
return (
<div>
<NavLink className="list-group-item " activeClassName="acn" {...this.props}/>
</div>
)
}
}

children属性用来传递标签体,可以在组件内部**使用this.props.children获取传递的组件标签体内容**,所以自定义组件标签MyNavLink可以写成自结束标签,在MyNavLink组件内部**使用对象的结构赋值的方式{...this.props}获取标签属性**,所以以下两者是等价的:

<MyNavLink to="/about" children="about"/>    <=>    <MyNavLink to="/about">about</MyNavLink>

11.4 switch标签

路由一般一个路径对应一个组件,但是默认一个路径path依旧还会再往后的代码去找是否有与其对应的组件component属性,这样的操作是多余的,为了不让react做这样的匹配,只需在路由链接标签外加上<Switch>标签即可。

import {Switch,Route} from 'react-router-dom'

<div className="panel-body">
{/* 注册路由 */}
<Switch>
<Route path="/about" component={About}/>
<Route path="/home" component={Home}/>
</Switch>
</div>

11.5 解决多级路径页面刷新样式丢失

1. public/index.html中引入样式不写./写/

   ./代表的是相对路径从当前路径下开始匹配

   /代表从项目根路径开始匹配

2. public/index.html中引入样式时不写./写%PUBLIC_URL%

   %PUBLIC_URL%代表public文件的绝对路径        

3. 使用HashRouter

   HashRouter将#放在项目根路径后面,#后面的路径一律不提交服务器,视作前端资源。

11.6  严格匹配

默认开启的是模糊匹配,即输入的路径必须包含匹配的路径。

保证路由链接和注册路由的路径是一致时才会触发路由,一般不开启,除非路径匹配出现问题。有时开启会导致无法继续匹配二级路由。

<div className="col-xs-2 col-xs-offset-2">
<div className="list-group">
{/* 编写路由链接 */}
<MyNavLink to="/about/a" children="about"/>
<MyNavLink to="/home" children="home"/>
</div>
</div>
<div className="col-xs-6">
<div className="panel">
<div className="panel-body">
{/* 注册路由 */}
<Switch>
<Route exact path="/about" component={About}/>
<Route exact={true} path="/home" component={Home}/>
</Switch>
</div>
</div>
</div>

给路由标签加上exact属性,它是一个布尔值,上述案例中,不添加exact依旧可以展示About组件,但添加后开启了严格匹配模式,/about/a和/about不一致,所以当点击about标签时就不会显示About组件。

11.7  redirect的使用

一般写在所有路由注册的最下方,当所有路由都无法匹配时,跳转到redirect指定的路由;

import {Switch,Route,Redirect} from 'react-router-dom' 

<Switch>
<Route path="/about" component={About}/>
<Route path="/home" component={Home}/>
<Redirect to="/about"/>
</Switch>

11.8 嵌套路由

1.注册子路由的时候要写上父路由的path值;

2.路由的匹配是按照注册顺序进行的(父级路由-->子路由);

   <ul className="nav nav-tabs">
<li>
<MyNavLink to="/home/news" children="news"/>
</li>
<li>
<MyNavLink to="/home/messages" children="messages"/>
</li>
</ul>

3. 注册路由;

   <Switch>
<Route path="/home/news" component={News} />
<Route path="/home/messages" component={Messages} />
<Redirect to="/home/news"/>
</Switch>

11.9 路由组件传参

11.9.1 向路由组件传递params参数

1. 携带参数:

<ul>
{
this.state.list.map(item => {
return (
<li key={item.id}>
{/* 1. 向路由组件传递参数 */}
<Link to={`/home/messages/message1/${item.id}/${item.content}`}>{item.content} </Link>
</li>
)
})
}
</ul>

2. 声明接收:

{/* 2. 声明接收params参数 */}
<Route path="/home/messages/message1/:id/:content" component={Details}></Route>

3. 接收参数

   在Detail组件中:

 // 3. 接收参数
const {id,content} = this.props.match.params;

11.9.2 向路由组件传递search参数

1. 携带参数

<Link to={`/home/messages/message1?id=${item.id}&content=${item.content}`}>{item.content}</Link>

2. 注册路由:无需声明,正常注册即可

<Route path="/home/messages/message1" component={Details}></Route>

3.  接收参数:获取的search是urlencoded编码字符,需要使用querystring解析

import qs from 'querystring'

const search = this.props.location.search.slice(1);
const {id,content} = qs.parse(search);

11.9.3 向路由组件传递state参数

它不会将传递的参数在地址栏中展示出来

1.携带参数:将to属性写成一个对象,pathname属性指定路由路径,state属性指定传递的参数

<Link to={{pathname:'/home/messages/message1',state:{id:item.id,content:item.content}}}>{item.content}</Link>

2.注册路由:

<Route path="/home/messages/message1" component={Details}/>

3.接收参数:

const {id,content} = this.props.location.state;

11.10 编程式路由导航

切换路由组件有时不一定依靠点击Link实现跳转,我们可以在其它的地方实现路由跳转

实现如下效果点击replace,以replace的方式跳转路由;点击push以push的方式跳转路由(replace跳转实际上是替换栈顶的路径,push跳转是将路径入栈);点击前进按钮前进到下一个页面路径,点击浏览器后退回上一个页面路径。

 

changeMode = (mode,id,content) => {
    if(mode === 'replace') {
        // params
        // this.props.history.replace(`/home/messages/message1/${id}/${content}`);
        // search 
        // this.props.history.replace(`/home/messages/message1?id=${id}&content=${content}`);
        // state
        this.props.history.replace('/home/messages/message1',{id,content});
    } else if(mode === 'push') {
        // this.props.history.push(`/home/messages/message1/${id}/${content}`);
        // search 
        // this.props.history.push(`/home/messages/message1?id=${id}&content=${content}`);
        // state
        this.props.history.push('/home/messages/message1',{id,content});
    }
}


forward = () => {
    this.props.history.goForward();
}

back = () => {
    this.props.history.goBack();
}

go = () => {
    this.props.history.go(-2);
}

 render() {
     return (
         <div>
             <ul>
                 {
                     this.state.list.map(item => {
                         return (
                             <li key={item.id}>
                                 <button onClick={() => this.changeMode('replace',item.id,item.content)}>replace</button>
                                 <button onClick={() => this.changeMode('push',item.id,item.content)}>push</button>
                                 {/* 1. 向路由组件传递参数 */}
                                 {/* <Link to={`/home/messages/message1/${item.id}/${item.content}`}>{item.content}</Link>&nbsp;&nbsp; */}
                                 {/* 1. 传递search参数 */}
                                 {/* <Link to={`/home/messages/message1?id=${item.id}&content=${item.content}`}>{item.content}</Link> */}
                                 {/* 1. 传递state参数 */}
                                 <Link to={{pathname:'/home/messages/message1',state:{id:item.id,content:item.content}}}>{item.content}</Link>
                             </li>
                         )
                     })
                 }
             </ul> 
             <hr />
             {/* 2. 声明接收params参数 */}
             {/* <Route path="/home/messages/message1/:id/:content" component={Details}></Route> */}
             {/* 2. 接收search参数 */}
             <Route path="/home/messages/message1" component={Details}></Route>
             <button onClick={this.forward}>前进</button>
             <button onClick={this.back}>后退</button>
             <button onClick={this.go}>go</button>
         </div>
     )
 }

总结:编程式路由导航实际上时使用了

11.11 withRouter 

在react中存在两种组件,一般组件和路由组件。在一般组件中如果需要执行一些路由组件的API时,由于一般组件不存在props.history这个属性,操作无法进行。故需要withRouter的包装。

将上述案例中的前进后退按钮放在标题下方

在一般组件Topic中

import React, { Component } from 'react'
import { withRouter } from 'react-router';

class Header extends Component {
forward = () => {
this.props.history.goForward();
}

back = () => {
this.props.history.goBack();
}

go = () => {
this.props.history.go(-2);
}

render() {
return (
<div>
<div className="col-xs-offset-2 col-xs-8">
<div className="page-header">
<h2>React Router Demo</h2>
<button onClick={this.forward}>前进</button>
<button onClick={this.back}>后退</button>
<button onClick={this.go}>go</button>
</div>
</div>
</div>
)
}
}
export default withRouter(Header)

withRouter可以加工一般组件,让一般组件具备路由所有的API;

withRouter返回值时一个新组件;

 11.12 BrowserRouter和HashRouter的对比

12. redux

12.1 redux的基本原理

12.2 求和案例

1. 首先取出掉count组件的自身状态

2. 在src下创建redux文件

count_action:用于创建action对象; 

// 专门为count组件生成action对象
import {INCREMENT,DECREMENT} from './constant'

export const createIncrement = (data) => ({type:INCREMENT,data});
export const createDecrement = (data) => ({type:DECREMENT,data});

constant:存放type值,方便管理,减少错误;

   // 用来定义action对象中中type类型的常量值,方便常量管理
export const INCREMENT = 'increment';
export const DECREMENT = 'decrement';

3. 在store.js中 

-引入createStore函数,创建一个store

-createStore函数调用时传入一个为其服务的reducer

-暴露store供外部使用

// 改文件用来暴露一个store对象

// 引入createStore,用于创建redux中最为核心的store对象
import {createStore} from 'redux'
// 引入为count组件服务的reducer
import countReducer from './count_reducer'
// 暴露store
export default createStore(countReducer);

4. count_reducer.js
   -reducer本质是一个函数,两个参数:preState,action,返回值是加工后的状态;

   -reducer两个作用:初始化状态,加工状态;

   -reducer被第一次调用时,是store自动触发的,传递的preState是undefined;

// 该文件是用来为count组件服务的reducer,reducer的本质就是一个函数
// 该函数会接收到两个参数:之前的状态(preState),动作对象(action)

const initState = 0
export default function countReducer(preState=initState,action) {
// 从action中获取对象
const {type,data} = action;
// 根据type的类型决定如何加工数据
switch(type) {
case 'increment' :
return preState + data;
case 'decrement' :
return preState - data;
default :
return preState;
}
}

5. count.jsx中,需要导入store,在需要改变状态的回调中,这里以handleOddPlus为例,使用store.dispatch(action)修改状态,使用store.getState()获取当前的状态(即reducer的返回值);

import React, { Component } from 'react'
import store from '../../redux/store'
import './index.css'

export default class Add extends Component {
handlePlus = () => {
const {value:input} = this.selector;
// dispatch参数传递一个action对象
// select表单拿到的value值是字符串
store.dispatch({type:'increment',data:input * 1});
}

handleMinus = () => {
const {value:input} = this.selector;
store.dispatch({type:'decrement',data:input * 1});
}

handleOddPlus = () => {
const {value:input} = this.selector;
// 获取当前状态
const count = store.getState();
if(count % 2 !== 0){
store.dispatch({type:'increment',data:input * 1});
}
}

handlePlusAsync = () => {
const {value:input} = this.selector;
setTimeout(() => {
store.dispatch({type:'increment',data:input * 1});
},2000)
}

render() {
return (
<div className="wrapper">
<div className="wrapper-title">
<h2>当前求和为:{store.getState()}</h2>
</div>
<select ref={c => this.selector = c} name="select">
<option value="1">1</option>
<option value="2">2</option>
<option value="3">3</option>
</select>
<button onClick={this.handlePlus}>加</button>
<button onClick={this.handleMinus}>减</button>
<button onClick={this.handleOddPlus}>当前时奇书加</button>
<button onClick={this.handlePlusAsync}>异步加</button>
</div>
)
}
}

6. 在index.js中检测store的状态改变,一旦发生改变重新渲染<App/>;

   redux值负责状态管理,至于状态的改变驱动页面的展示需要自己实现。

   // 引入react核心库
import React from 'react'
// 引入reactDOM
import ReactDOM from 'react-dom'
// import {BrowserRouter} from 'react-router-dom'
import store from './redux/store'
// 引入App组件
import App from './App'

// 渲染App组件到页面
ReactDOM.render(<App/>,document.getElementById('root'));
// 检测redux中的状态变化,变化就调用render
store.subscribe(() => {
ReactDOM.render(<App/>,document.getElementById('root'));
});

12.3 异步action

当延迟的动作不想交给组件自身,而是交给action;

何时需要异步action:想要对状态进行操作,但是具体的数据靠异步任务返回;

实现过程:

1. 安装:npm i redux-thunk 

2. 在store中做相关配置:需要额外引入applyMiddleware和安装的redux-thunk,将applyMiddleware作为createStore的第二个参数;

 // 引入createStore,用于创建redux中最为核心的store对象
import {createStore,applyMiddleware} from 'redux'
// 引入为count组件服务的reducer
import countReducer from './count_reducer'
// 引入redux-thunk用于支持异步actions
import thunk from 'redux-thunk'
// 暴露store
export default createStore(countReducer,applyMiddleware(thunk));

3. 异步任务有结果后,分发一个同步的action去真正操作数据;

conut.jsx

   handlePlusAsync = () => {
const {value:input} = this.selector;
store.dispatch(createIncrementAsync(input * 1,2000));
// setTimeout(() => {
// store.dispatch(createIncrement(input * 1));
// },2000)
}

 count_action.js

   export const createIncrement = (data) => ({type:INCREMENT,data});
export const createIncrementAsync = (data,time) => {
return (dispatch) => {
setTimeout(() => {
dispatch(createIncrement(data));
},2000);
}
}

在conut组件中调用dispatch后,store会**获得一个函数作为执行结果**,通过安装中间件thunk,使得store去执行这个函数(即createIncrementAsync返回的箭头函数),该函数的函数体是一个异步任务,该异步任务执行完毕,再次调用dispatch,将真正的action对象传递给store。

12.4 react-redux

UI组件:不适用任何redux的api,只负责页面的呈现和交互;

容器组件:负责和redux通信,将结果递交给UI组件;

修改求和案例:

1.创建容器组件:

 在src布鲁下新建container文件夹,用来存放容器组件;

创建一个容器组件,依靠react-redux的connect函数

connect(mapStateToProps,mapDispatchToProps)(UI组件),需要传递两个参数,类型是函数;

​    -mapStateToProps:**映射状态**,返回一个对象;

​    -mapDispatchToProps:**映射操作状态的方法**,返回值是一个对象;

定义这两个函数,return的对象的属性**会以props的形式传递给UI组件**

container/Count/index.jsx:

// 引入count的UI组件
import CountUI from '../../Components/Add'
import {createIncrement,createDecrement,createIncrementAsync} from '../../redux/count_action'
// 引入connect用于链接UI组件和redux
import {connect} from 'react-redux'

// 该函数返回的是一个对象
// 返回对象的key就作为传递给UI组件props的keys,返回的value就作为传递给UI组件props的value
// 该函数用于传递状态
function mapStateToProps(state) {
return {count:state}
}

// 该函数返回的是一个对象,用于传递状态的方法
function mapDispatchToProps(dispatch) {
return {
plus: (number => dispatch(createIncrement(number))),
minus: (number => dispatch(createDecrement(number))),
plusAsync: ((number,time) => dispatch(createIncrementAsync(number,time)))
}
}

// 使用connect创建暴露一个Count的容器组件
export default connect(mapStateToProps,mapDispatchToProps)(CountUI)

2.在容器组件上级组件引入store

容器组建的store是靠props传递进去的,而不是直接在容器组件中import引入,需要在容器组件父组件中书写如下代码;

app.js:

import React,{Component} from "react";
import Add from './container/Conut'
import store from './redux/store'

export default class App extends Component {
render() {
return (
<Add store={store}/>
)
}
}

3.修改component/Count/index.jsx:

在UI组件中用props回去容器组件传来的状态和操作状态的方法

handlePlus = () => {
const {value:input} = this.selector;
this.props.plus(input * 1);
}

handleMinus = () => {
const {value:input} = this.selector;
this.props.minus(input * 1);
}

handleOddPlus = () => {
const {value:input} = this.selector;
// 获取当前状态
const count = this.props.count;
if(count % 2 !== 0){
this.props.plus(input * 1);
}
}

handlePlusAsync = () => {
const {value:input} = this.selector;
console.log(this.props);
this.props.plusAsync(input * 1,2000);
}

在控制台上输出this.props可见:

12.4.1 connect 参数优化

container精简版:

将函数直接写在connect的形参位置,mapDispatchToProps可以写成一个对象;

export default connect(
state => ({count:state})
,
{
plus:createIncrement,
minus:createDecrement,
plusAsync:createIncrementAsync
})(CountUI)

12.4.2 Provider组件

上述案例中容器组件的store是通过该容器组件的标签属性传递<Add store={store}/>,但如果容器组件的数量一多,需要挨个进行传递,而在react-redux中提供了Provider组件给需要store的容器组件传值。

app.js

import { Provider } from "react-redux";

export default class App extends Component {
render() {
return (
<Provider store={store}>
<Add />
</Provider>
)
}
}

另外,由于使用了react-redux的缘故,在index.js中无需在根组件外侧使用store.subscribe()进行监测页面的改变,即可以删你出上述案例的如下代码:

store.subscribe(() => {
ReactDOM.render(<App/>,document.getElementById('root'));
});

优化:

1. 容器组件个UI组件整合成一个组件;

   // 引入count的UI组件
import {createIncrement,createDecrement,createIncrementAsync} from '../../redux/count_action'
// 引入connect用于链接UI组件和redux
import {connect} from 'react-redux'
import React, { Component } from 'react'
import './index.css'

class Add extends Component {
handlePlus = () => {
const {value:input} = this.selector;
this.props.plus(input * 1);
}

handleMinus = () => {
const {value:input} = this.selector;
this.props.minus(input * 1);
}

handleOddPlus = () => {
const {value:input} = this.selector;
// 获取当前状态
const count = this.props.count;
if(count % 2 !== 0){
this.props.plus(input * 1);
}
}

handlePlusAsync = () => {
const {value:input} = this.selector;
console.log(this.props);
this.props.plusAsync(input * 1,2000);
}

render() {
return (
<div className="wrapper">
<div className="wrapper-title">
<h2>总价格为:{this.props.count}</h2>
</div>
<select ref={c => this.selector = c} name="select">
<option value="1">1</option>
<option value="2">2</option>
<option value="3">3</option>
</select>
<button onClick={this.handlePlus}>加</button>
<button onClick={this.handleMinus}>减</button>
<button onClick={this.handleOddPlus}>当前时奇书加</button>
<button onClick={this.handlePlusAsync}>异步加</button>
</div>
)
}
}

export default connect(
state => ({count:state})
, // 映射状态,参数state就是redux传给组件的状态,相当于调用了store.getState
{ // 映射操作状态的方法
plus:createIncrement,
minus:createDecrement,
plusAsync:createIncrementAsync
})(Add)

12.4 .3 使用redux实现多组件通信

1.修改store:

​    若需要store负责多个组件之间的通信,其createStore第一个参数应该是多个reducer的总状态,总状态需要combineReducer进行合并,合并后的总状态是一个对象。

// 引入createStore,用于创建redux中最为核心的store对象
import {createStore,applyMiddleware,combineReducers} from 'redux'
// 引入为count组件服务的reducer
import countReducer from './reducer/count_reducer'
import personReducer from './reducer/person_reducer'
// 引入redux-thunk用于支持异步actions
import thunk from 'redux-thunk'

const allReducer = combineReducers({
count:countReducer,
person:personReducer
})
// 暴露store
export default createStore(allReducer,applyMiddleware(thunk));

2.在容器组件中获取状态需要写明是从哪个reducer中获取的状态,即combineReducers中的属性名;

export default connect(
state => ({personList:state.person,total:state.count})
,
{
addPerson:createPerosn,
}
)(person)

 12.5 纯函数

只要是同样的输入,必定会得到同样的输出返回;

必须遵守以下一些约束:

​    不得改写参数数据

​    不会产生任何副作用,例如网络请求,输入和输出设备;

​    不能调用Date.now()或者Math.random()等不纯的方法;

redux的reducer函数必须是一个纯函数。

12.6 redux开发者工具

1. 安装:npm i redux-devtools-extension

2. store.js进行配置:

   import {composeWithDevTools} from 'redux-dextools-extension'

   const store = createStore(allReducer,composeWithDevTools(applyMiddleware(thunk)));

3.chrome安装扩展程序

这边选择从本地加载:

 

 

 添加到任务栏:

13 lazyLoad 懒加载

react的懒加载一般使用在路由组件加载中,为了让路由组件点击时再请求组件资源。

import React, { Component,lazy,Suspense } from 'react'
import Loading from './pages/Loading'

const About = lazy(() => import('./pages/About'));
const Home = lazy(() => import('./pages/Home'));
```

```react
{/* 注册路由 */}
<Suspense fallback={<Loading />}>
<Route path="/about" component={About}/>
<Route path="/home" component={Home}/>
<Redirect to="/about"/>
</Suspense>

Suspense标签需要添加fallback属性,用来展示需要加载的组件处于pending状态时展示的内容,其导入方式不能写成懒加载。

14 hooks

hooks是16.8版本的新特性,可以在函数式组件中使用state及其它的react特性;

三个常用的hook:

15.1.state hook

React.useState(init);

​    该方法的调用能返回一个数组,数组中只有两个元素 ,

第一个是状态,第二个是改变状态的方法;​    该方法会调用多次,第一次调用会将第一个值缓存下来,所以后续调用状态不会被init覆盖;

<script type="text/babel">
    function Demo() {

        const [count,setCount] = React.useState(0);
        const [name,setName] = React.useState('Jack');

        function add() {
            // 写法一:参数直接指定新的状态
            setCount(count + 1);
            // 写法二:传递一个函数,参数是之前的状态值,返回修改后的状态
            setCount(count => count + 1);
        }

        function changeName() {
            setName(name => 'Tom');
        }
        return (
            <div>
                <h2>当前求和为:{count}</h2>    
                <h2>我的名字是:{name}</h2>
                <button onClick={add}>+ 1</button>
                <button onClick={changeName}>改名</button>
            </div>
        )
    }

    ReactDOM.render(<Demo/>,document.getElementById('test'));
</script>

15.2 Effect hook

可以在函数组件中模拟生命周期钩子。

语法说明:

React.useEffect(() => {
    // 在再次可以执行任何副作用操作
    return () => {}    // 组件卸载时执行
},[stateValue]);

第一个参数是一个函数,该函数相当于:componentDidMount(),componentDidUpdate(),componentwillUnmount();该函数返回一个匿名函数,该函数会在组件卸载时执行相当于componentwillUnmount();函数存在第二个参数,该参数是一个数组,数组元素为监视的状态。

​    1.如果数组中有元素,则该函数第一个参数同时充当componentDidUpdate(),即当数组状态元素发生改变时触发;

​    2,如果为空数组则第一个参数不会监视任何状态的改变,不具有componentDidUpdate()功能;

​    3.如果无第二个参数,则参数一会监视函数组件所有状态的变化。```react

function Demo() {

    const [count,setCount] = React.useState(0);
    const [name,setName] = React.useState('Jack');

    function add() {
        // 写法一:参数直接指定新的状态
        setCount(count + 1);
        // 写法二:传递一个函数,参数是之前的状态值,返回修改后的状态
        setCount(count => count + 1);
    }

    React.useEffect(() => {
        let timer = setInterval(() => {
            setCount(count => count + 1);
        },1000);
        return () => {
            clearInterval(timer);
        }
    },[]);

    function changeName() {
        setName(name => 'Tom');
    }

    function unmount() {
        ReactDOM.unmountComponentAtNode(document.getElementById('test'));
    }
    return (
        <div>
            <h2>当前求和为:{count}</h2>    
            <h2>我的名字是:{name}</h2>
            {/*<button onClick={add}>+ 1</button>*/}
            <button onClick={changeName}>改名</button>
            <button onClick={unmount}>卸载组件</button>
        </div>
    )
}

ReactDOM.render(<Demo/>,document.getElementById('test'));

15.3 Ref hook

function Demo() {

    const myRef = React.useRef();

    function showData() {
        alert(myRef.current.value)

        return (
            <div>
                <input type="text" ref={myRef}/>
                <button onClick={showData}>提示输入数据</button>
            </div>
        )
    }

    ReactDOM.render(<Demo/>,document.getElementById('test'));

16 Context

能实现组件之间的通信,**祖组件和后代组件之间**的通信。

const MyContext = React.createContext();
class A extends React.Component {
    state = {
        username:'zs',
        age:19
    }
    render(){
        const {username,age} = this.state;
        return (
            <div className="a">
                <h1>这是A组件</h1>
                <h1>我的用户名为:{username}</h1>
                <MyContext.Provider value={{username,age}}>
                    <B/>
                </MyContext.Provider>

            </div>
        )
    }
}

class B extends React.Component{
    render(){
        return (
            <div className="b">
                <h1>这是B组件</h1> 
                <C/>   
            </div>
        )
    }
}
class C extends React.Component{
    static contextType = MyContext;
    render(){
        console.log(this.context);
        return (
            <div className="c">
                <h1>这是C组件</h1>   
                <h1>接收到A组件传来的username为:{this.context.username}</h1> 
            </div>
        )
    }
}
ReactDOM.render(<A/>,document.getElementById('root'));

若组件C为函数式组件:

function C() {
    return (
        <div className="c">
            <h1>这是C组件</h1>   
            <h1>接收到A组件传来的username为:
                <MyContext.Consumer>
                    {value => value.username}
                </MyContext.Consumer>
            </h1> 
        </div>
    )
}


17. 组件优化

一般情况下,只要执行了setState(),即使不改变状态数据,组件也会重新render(),这样效率比较低;只要当前组件重新render了就会自动冲新render子组件,纵使子组件没有用到父组件的任何数据。

优化方案:只有当组件的state和props数据发生改变的时候才会重新render(),那是由于shouldComponentUpdate()总是返回true。

解决:

    这只是进行了数据的浅比较,如果只是数据对象内部发生了改变,返回false

代码:

18. renderProps

作用:向组件内部传入带有动态内容的结构;<=> vue中插槽技术;

1. 使用children props通过组件标签体传入结构;

   如果B组件需要A组件中的数据无法做到。

2. 使用render props 通过组件标签属性传入结构,一般用render函数属性;

class Demo extends React.Component {
       render(){
           return (
               <A render={(data)=> <B data={data}></B> }></A>
           )
       }
   }
   class A extends React.Component {
       state = {name:'zs'}
       render(){
           const {name} = this.state;
           console.log(name);
           return(
               <div> 
                   <p>这是A组件</p>
                   {this.props.render(name)}    
               </div>
           )
       }
   }
   class B extends React.Component {
       render(){
           console.log(this.props.data);
           return(
               <div>
                   <p>这是B组件</p>
                   <p>{this.props.data}</p>    
               </div>
           )
       }
   }
   ReactDOM.render(<Demo/>,document.getElementById('root')); 

19.错误边界

 

子组件中出错,会影响到父组件,为了控制错误不影响到父组件,引入错误边界

错误边界可以捕获后代组件生命周期返回的错误;

错误边界始终找出错组件的父组件去处理;

 

举报

相关推荐

0 条评论