React 0基础学习路线(3)— 详述组件基础及props和state原理(附详细案例代码解析过程)

1. 重点提炼

1. 1 组件

  • 函数式组件

    • 可以通过函数的第一个参数 props来接收数据
    • 函数到返回值就是组件的视图
  • 类式组件

    • 通过 class类的定义
    • 需要继承自 React.Component
    • 可以通过类的 props属性来接收传入的数据
    • 类组件拥有:state,组件内部的私有状态(数据)
    • 通过类的 render返回组件的视图
  • state

    • 类的内部私有状态(数据)
    • 需要在类的构造函数中进行初始化
      • 如果一个组件类重写类构造函数,那么就需要手动的调用 super,并传入 props
    • 如果需要对组件的 state 进行修改的时候,需要调用组件对象的 setState 方法
    • setState接收的一个对象或者函数
      • 如果是对象的,那么这个对象会被合并到组件的 state
      • setState 是异步
      • setState 是更新合并的(多次调用会合并)
      • 还可以通过传入回调函数的形式对state进行修改
    • setState 调用的时候,会调用 render 方法,对视图进行重新渲染
  • 事件

    • 通过行间 on事件名称的形式来绑定的
    • 事件名称是驼峰命名规则:onMouseOver
    • 事件绑定的函数
      • this 默认为 undefined
      • 事件函数的第一个参数默认为:event对象
        • 我们可以在事件函数通过event 对象来访问具体的 dom 元素,还可以阻止默认行为、阻止冒泡等
      • 我们可以通过 bind方法来绑定this,然后再赋值给事件
  • stateprops

  • props 是外部传入的数据

    • 组件内部只能使用,而不能直接修改

    • state 是组件内部私有数据

      • 组件内部可以访问,也可以通过 setState 进行修改
      • 组件的外部不能访问某个组件内部的state
  • 如果需要修改props

    • 由外部使用该组件的父级通过 props 传入一个函数

    • 子级通过 this.props函数 去调用,并传入想要修改的数据,通知父级进行修改(伪代码演示)

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
 
<script>
 
    let user = {username: 'zs', age: 35};
 

    // 为什么不推荐子级直接修改父级参数值?因为假如别的组件也要用这个变量(作为参数),就可能会出现问题!
    child( user );
    child2( user );
 
    console.log(user);
 
    function child(data) {
        // 类似自定义组件
        data.username = 'ls'; // 子级不能直接修改父级传递的参数,但是如果传递的是props是对象,子级是可以直接修改父级参数的,但是不推荐这样做!
    }
 
    function child2(data) {
        // 这里可能也用到了user
        console.log(data);
    }
//注意:数据不是谁想改就能改的,而是由所有者负责修改!这块由父级决定,子级申请修改!
 
</script>
 
</body>
</html>

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
 
<script>
 
    let user = {username: 'zs', age: 35};
 
    function onChange(v) {
        user.username = v;
    }
 
    //父级调用子级函数
    child( user, onChange );
    // 为什么不推荐子级直接修改父级参数值?因为假如别的组件也要用这个变量(作为参数),就可能会出现问题!
    child2( user );
 
    console.log(user);
 
    function child(data, onChange) {
        // 类似自定义组件
        // data.username = 'ls'; 子级不能直接修改父级传递的参数,但是如果传递的是props是对象,子级是可以直接修改父级参数的,但是不推荐这样做!
 
        //  如果要修改父级传递参数,应该告知数据持有人。问他是否允许改,如果它同意则改!(通过接收回调函数作为参数!)
        // 如果要修改则调用 onChange,onChange是由父级类定义的
        onChange('ls');
    }
 
    function child2(data) {
        // 这里可能也用到了user
        console.log(data);
    }
//注意:数据不是谁想改就能改的,而是由所有者负责修改!这块由父级决定,子级申请修改!
 
</script>
 
</body>
</html>

2. 脚手架构建

create-react-app app

  构建完,运行看看是否通过

npm start

image-20200701210756159

  删除一些不需要的文件

image-20200701211841131

src/index.js 去掉一些不需要的引入。(注释部分)

image-20200701211402572

src/App.js去掉一些不需要的引入。(注释部分)

image-20200701211958549

2. 1 example01

app\src\App.js

import React from 'react';

function App() {
  return (
    <div className="App">
      <h1>CSDN</h1>
    </div>
  );
}

export default App;

image-20200601095822903

参考:https://github.com/6xiaoDi/blog-react-Novice/tree/V0.1

Branch:branch1

tag:v0.1

2. 2 组件

  web 组件(其实html就是组件,组件其实具有独立功能的最小单元)就是对 web 中的数据、结构、方法等进行封装,复用,与 JavaScript 中功能函数封装类似,但更关注的是对 web 元素(标签)的封装与扩展(原生提供扩展:webComponent<自己可自定义标签,进行扩展>,React就是出于这种思想而来)

  我们在js中通过函数的方式封装一个选项卡,然后通过调用该函数,就可以在页面中指定的位置显示该选项卡,那么在React中我们把这样的一个函数称为组件(它具有一定独立的功能),我们以html标签的形式进行调用。实际上它就是自定义html标签,可以自定义结构、样式、功能。

* - 自定义html标签
*      - 结构
*      - 样式
*      - 功能

React 提供了两种组件构建方式

  • 函数式组件
  • 类式组件

2. 2. 1 函数式组件

  在 React.js 中,定义一个组件的最简单的方式就是 函数

function myComponent() {
    return (
        <div>
            <h2>我的组件</h2>
        </div>
    );
}
ReactDOM.render(
    <myComponent />,
    document.getElementById('app')
);
  • 函数的名称就是组件的名称
  • 函数的返回值就是组件要渲染的内容

函数的名称(组件的名称),必须是首字母大写

  注意JSX的解析要依赖于React,如果没加此依赖,可能会报错!即import React from 'react';

2. 2. 1. 1 example02

App.js

import React from 'react';
 
import Custom from './components/custom';
 
function App() {
  return (
    <div className="App">
      <h1>CSDN</h1>
      <hr />
      {Custom()}
    </div>
  );
}
 
export default App;

src/components/custom.js

import React from 'react';
 
export default function Custom() {
    return (
        <div>
            <h2>CSDN</h2>
            <div>
            </div>
        </div>
    );
}

image-20200601104657228

参考:https://github.com/6xiaoDi/blog-react-Novice/tree/V0.11

Branch:branch1

tag:v0.11

  但是这里我们当作函数去用,还不算太好。React为我们提供了更加方便的方式,如果函数调用返回函数式组件(JSX),我们可以像标签一样去使用这个函数,它会自动解析。

function App() {
  return (
    <div className="App">
      <h1>CSDN</h1>
      <hr />
      {/*{Custom()}*/}
      <Custom />
    </div>
  );
}

image-20200601104657228

参考:https://github.com/6xiaoDi/blog-react-Novice/tree/V0.12

Branch:branch1

tag:v0.12

* 函数组件
*  - 通过函数来封装一个组件,函数的返回值将作为该组件渲染的结构

注意:

* 自定义的组件与html原生组件(标签)
*  - 原生的标签使用 小写
*  - 自定义的组件名称必须式大写

2. 2. 2 类式组件

  我们还可以通过 类(class)类定义组件

* 类组件
*  - 通过类来封装一个组件,该类必须继承 React.Component 的基类
*  - 类中必须包含一个 render 方法
class Component01 extends React.Component {
    render() {
        return (
            <div>
                <h2>我的组件!</h2>
            </div>
        );
    }
}
  • 组件类必须继承 React.Component
  • 组件类必须有 render 方法
  • render 方法的返回值就是组件要渲染的内容

类的名称(组件的名称),也必须是首字母大写

2. 2. 2. 1 example03

src/components/custom02.js

import React from 'react';
// 继承React.Component类
export default class Custom02 extends React.Component {
 
    render() {
        return (
            <div>
                <h2>React.Component -!</h2>
            </div>
        );
    }
 
}

src/App.js

import React from 'react';
 
import Custom from './components/custom';
import Custom02 from './components/custom02';
 
function App() {
  return (
    <div className="App">
      <h1>CSDN</h1>
      <hr />
      {/*{Custom()}*/}
      {/*<Custom />*/}
      <Custom02 />
    </div>
  );
}
 
export default App;
image-20200601110313338

参考:https://github.com/6xiaoDi/blog-react-Novice/tree/V0.13

Branch:branch1

tag:v0.13

2. 3 组件案例

example-friendList

样式

.friend-list {
    border: 1px solid #000000;
    width: 200px;
}
.friend-group dt {
    padding: 10px;
    background-color: rgb(64, 158, 255);
    font-weight: bold;
}
.friend-group dd {
    padding: 10px;
    display: none;
}
.friend-group.expanded dd {
    display: block;
}
.friend-group dd.checked {
    background: green;
}

2. 3. 1 创建 FriendList 组件

class FriendList extends React.Component {
      render() {
          return (
              <div className="friend-list">
                <div className="friend-group">
                    <dt>家人</dt>
                    <dd>爸爸</dd>
                    <dd>妈妈</dd>
                </div>
                <div className="friend-group">
                    <dt>朋友</dt>
                    <dd>张三</dd>
                    <dd>李四</dd>
                    <dd>王五</dd>
                </div>
                <div className="friend-group">
                    <dt>客户</dt>
                    <dd>阿里</dd>
                    <dd>腾讯</dd>
                    <dd>头条</dd>
                </div>
            </div>
        );
    }
}

2. 3. 1. 1 example04

src/components/friendList/index.css

.friend-list {
    border: 1px solid #000000;
    width: 200px;
}
.friend-group {
    margin: 0;
}
.friend-group dt {
    padding: 10px;
    background-color: rgb(64, 158, 255);
    font-weight: bold;
}
.friend-group dd {
    padding: 10px;
    /*先注释display样式*/
    display: -none;  
}
.friend-group.expanded dd {
    display: block;
}
.friend-group dd.checked {
    background: green;
}

src/components/friendList/index.js

import React from 'react';
import './index.css';
 
export default class FriendList extends React.Component {
    render() {
        return(
            <div>
                <h2>好友列表</h2>
                <div className="friend-list">
                    <div className="friend-group">
                        <dt>家人</dt>
                        <dd>爸爸</dd>
                        <dd>妈妈</dd>
                    </div>
                    <div className="friend-group">
                        <dt>朋友</dt>
                        <dd>张三</dd>
                        <dd>李四</dd>
                        <dd>王五</dd>
                    </div>
                    <div className="friend-group">
                        <dt>客户</dt>
                        <dd>阿里</dd>
                        <dd>腾讯</dd>
                        <dd>头条</dd>
                    </div>
                </div>
            </div>
        )
    }
 
}

src/App.js

import React from 'react';
 
import Custom from './components/custom';
import Custom02 from './components/custom02';
 
//index.js默认加载可以省略
 
import FriendList from "./components/friendList";
 
function App() {
  return (
    <div className="App">
      <h1>CSDN</h1>
      <hr />
      {/*{Custom()}*/}
      {/*<Custom />*/}
      {/*<Custom02 />*/}
      <FriendList />
    </div>
  );
}
 
export default App;

  这里在页面中加入了一个好友面板组件,面板分为三个组,每个组有不同的数据。我们开发的时候要形成组件化思想(每个组件看出独立化的单元),这里的好友面板是一个组件,每一组又是一个独立单元。我们再App.js里,我们是把好友面板组件当成一个独立单元去应用。当我们进入好友面板组件的index.js,再把每个分组看成一个独立的单元,可再细分组件。

image-20200601133757091

参考:https://github.com/6xiaoDi/blog-react-Novice/tree/V0.14

Branch:branch1

commit description:v0.14-example04

tag:v0.14

2. 3. 2 组件复用 - 数据抽取

  为了提高组件的复用性,通常会把组件中的一些可变数据提取出来

let datas = {
    family: {
        title: '家人',
        list: [
            {name: '爸爸'},
            {name: '妈妈'}
        ]
    },
    friend: {
        title: '朋友',
        list: [
            {name: '张三'},
            {name: '李四'},
            {name: '王五'}
        ]
    },
    customer: {
        title: '客户',
        list: [
            {name: '阿里'},
            {name: '腾讯'},
            {name: '头条'}
        ]
    }
};

2. 3. 2. 1 example05

  需求:动态数据渲染分组

  首先得把数据准备好,由于数据数量少,我们暂时定义在。

  组件封装完毕后需要抽取,如果想要复用,则需要把组件中可变的部分抽取出来,作为参数传递。

  (类似)函数调用:将数据作为参数传递

app\src\App.js

import React from 'react';
 
import Custom from './components/custom';
import Custom02 from './components/custom02';
 
//index.js默认加载可以省略
 
import FriendList from "./components/friendList";
 
let datas = {
    family: {
        title: '家人',
        list: [
            {name: '爸爸'},
            {name: '妈妈'}
        ]
    },
    friend: {
        title: '朋友',
        list: [
            {name: '张三'},
            {name: '李四'},
            {name: '王五'}
        ]
    },
    customer: {
        title: '客户',
        list: [
            {name: '阿里'},
            {name: '腾讯'},
            {name: '头条'}
        ]
    }
};
 
function App() {
  return (
    <div className="App">
      <h1>CSDN</h1>
      <hr />
      {Custom(datas)}
      {/*<Custom />*/}
      {/*<Custom02 />*/}
      <FriendList />
    </div>
  );
}
 
export default App;

src/components/custom.js

import React from 'react';
 
export default function Custom(props) {
    console.log(props);
    return (
        <div>
            <h2>CSDN</h2>
            <div>
            </div>
        </div>
    );
}

image-20200601134820480

参考:https://github.com/6xiaoDi/blog-react-Novice/tree/V0.15

Branch:branch1

commit description:v0.15-example05

tag:v0.15

2. 3. 3 组件复用 - 数据传入

  数据虽然分离了,但是为了降低组件与数据之前的依赖,我们应该尽量避免在组件内部直接访问数据,通过传参的方法来进行解耦。

2. 3. 3. 1 example06

  以后开发过程中要形成组件化思想,即自定义标签构建,而不是函数调用了。

  需求:通过标签传参,借用标签属性

src/App.js

import React from 'react';
 
import Custom from './components/custom';
import Custom02 from './components/custom02';
 
//index.js默认加载可以省略
 
import FriendList from "./components/friendList";
 
let datas = {
    family: {
        title: '家人',
        list: [
            {name: '爸爸'},
            {name: '妈妈'}
        ]
    },
    friend: {
        title: '朋友',
        list: [
            {name: '张三'},
            {name: '李四'},
            {name: '王五'}
        ]
    },
    customer: {
        title: '客户',
        list: [
            {name: '阿里'},
            {name: '腾讯'},
            {name: '头条'}
        ]
    }
};
 
function App() {
  return (
    <div className="App">
      <h1>CSDN</h1>
      <hr />
      {/*{Custom(datas)}*/}
      <Custom datas={datas}/>
      {/*<Custom02 />*/}
      <FriendList />
    </div>
  );
}
 
export default App;

  接收到的是一个对象,对象里包着datas

image-20200601135125683

参考:https://github.com/6xiaoDi/blog-react-Novice/tree/V0.16

Branch:branch1

commit description:v0.16-example06

tag:v0.16

2. 3. 3. 2 example07

  通常我们把参数设置为props,传递的参数在props.datas底下:

src/components/custom.js

import React from 'react';
 
export default function Custom(props) {
    console.log(props.datas);
    return (
        <div>
            <h2>CSDN</h2>
            <div>
            </div>
        </div>
    );
}

image-20200601135425103

参考:https://github.com/6xiaoDi/blog-react-Novice/tree/V0.17

Branch:branch1

commit description:v0.17-example07

tag:v0.17

2. 3. 3. 3 example08

  如果想传递多个参数:

src/App.js

function App() {
    return (
        <div className="App">
            <h1>CSDN</h1>
            <hr />
            {/*{Custom()}*/}
            {<Custom datas={datas} v={1} k={'kkk'} />}
            {/*<Custom02 />*/}
 
            <FriendList />
        </div>
    );
}

src/components/custom.js

import React from 'react';
 
export default function Custom(props) {
    console.log(props);
    return (
        <div>
            <h2>CSDN</h2>
            <div>
            </div>
        </div>
    );
}

image-20200601135644448

参考:https://github.com/6xiaoDi/blog-react-Novice/tree/V0.18

Branch:branch1

commit description:v0.18-example08

tag:v0.18

2. 3. 3. 4 property

  使用组件的时候通过 标签属性-property 的方式传入数据,在组件内部通过构造函数参数(如果是函数组件,则通过函数参数)来接收传入的数据

<组件名称 属性名称="值" />
// 使用表达式
<组件名称 属性名称={表达式} />
ReactDOM.render(
    <FriendList datas={datas} />,
    document.getElementById('app')
);
2. 3. 3. 4. 1 example09

  以上就是函数式组件传参的方式,和函数调用区别不大。

  如果是一个类组件,其实差不多!

/**
* 在类组件中,通过标签属性传入的数据是保存到类中一个属性:props
*/

src/index.js

import React from 'react';
 
import Custom from './components/custom';
import Custom02 from './components/custom02';
 
//index.js默认加载可以省略
 
import FriendList from "./components/friendList";
 
let datas = {
    family: {
        title: '家人',
        list: [
            {name: '爸爸'},
            {name: '妈妈'}
        ]
    },
    friend: {
        title: '朋友',
        list: [
            {name: '张三'},
            {name: '李四'},
            {name: '王五'}
        ]
    },
    customer: {
        title: '客户',
        list: [
            {name: '阿里'},
            {name: '腾讯'},
            {name: '头条'}
        ]
    }
};
 
function App() {
  return (
    <div className="App">
      <h1>CSDN</h1>
      <hr />
      {/*{Custom(datas)}*/}
      {/*<Custom datas={datas} v={1} k={'kkk'}/>*/}
      {/*<Custom02 />*/}
      <FriendList datas={datas}/>
    </div>
  );
}
 
export default App;

src/components/friendList/index.js

import React from 'react';
import './index.css';
 
export default class FriendList extends React.Component {
 
    render() {
        console.log(this.props);
        return(
            <div>
                <h2>好友列表</h2>
                <div className="friend-list">
                    <div className="friend-group">
                        <dt>家人</dt>
                        <dd>爸爸</dd>
                        <dd>妈妈</dd>
                    </div>
                    <div className="friend-group">
                        <dt>朋友</dt>
                        <dd>张三</dd>
                        <dd>李四</dd>
                        <dd>王五</dd>
                    </div>
                    <div className="friend-group">
                        <dt>客户</dt>
                        <dd>阿里</dd>
                        <dd>腾讯</dd>
                        <dd>头条</dd>
                    </div>
                </div>
            </div>
        )
    }
 
}

image-20200601141040872

参考:https://github.com/6xiaoDi/blog-react-Novice/tree/V0.19

Branch:branch1

commit description:v0.19-example09

tag:v0.19

2. 3. 3. 5 接收参数 - props

  在组件对应的函数或类中通过:

  • 函数式组件:通过函数的第一个参数来接收
  • 类式组件:通过类的构造函数第一个参数来接收

  无论是函数式组件还是类式组件,都会把传入的参数组装成一个对象:

<组件名称 属性名称1="值1" 属性名称二={表达式二} />
 
// 函数式组件
function 组件名称(参数) {
      // 参数的结构为
      参数 = {
          属性名称1: "值1",
          属性名称二: 表达式二的值
    }
 
      return <div>组件结构</div>
}
// 类式组件
class 组件名称 extends React.Component {
      constructor(参数) {
          super(参数);
 
          this.props = {
              属性名称1: "值1",
              属性名称2: 表达式二的值
        }
    }
 
      render() {
          return <div>组件结构</div>
    }
}

在类式组件中,需要注意:

  • 当子类重写 constructor ,则必须调用父类 super - 类的知识点
  • 把接收到的参数传入父类构造函数,父类构造函数中会创建一个对象属性:props 类存储传入的参数数据

2. 3. 3. 6 通过参数动态渲染组件结构

class FriendList extends React.Component {
      constructor(props) {
          super(props);
    }
 
      render() {
          let {datas} = this.props;
          return (
              <div className="friend-list">
                    {Object.keys(datas).map((key, index) => (
                    <div className="friend-group" key={key}>
                        <dt>{datas[key].title}</dt>
                                {datas[key].list.map((list, index) => <dd key={index}>{list}</dd>)}
                    </div>
                ))}
            </div>
        );
    }
}
2. 3. 3. 6. 1 example10

  可直接对参数解构,循环进行渲染,为了读起来畅快,我们把render中的下一级"friend-group"取出,封装成函数组件,利用类组件和函数组件搭配应用。

src/components/friendList/index.js

import React from 'react';
import './index.css';
 
export default class FriendList extends React.Component {
 
    renderList() {
        let {datas} = this.props;
 
        // 取出json对象中所有key生成数组再遍历
        return Object.keys(datas).map( (key) => {
            let data = datas[key];
            return (
                <dl key={key} className="friend-group">
                    <dt>{data.title}</dt>
                </dl>
            )
        } )
    }
 
    render() {
        return(
            <div>
                <h2>好友列表</h2>
                <div className="friend-list">
                    {this.renderList()}
                </div>
            </div>
        )
    }
 
}

image-20200601142158314

参考:https://github.com/6xiaoDi/blog-react-Novice/tree/V0.20

Branch:branch1

commit description:v0.20-example10

tag:v0.20

2. 3. 3. 7 子组件提取

  组件与类一样,是一个不断提取的过程,当我们发现某个部分可复用或者结构复杂的时候,我们可以对它再次进行提取

class FriendGroup extends React.Component {
        constructor(props) {
                super(props);
    }
 
      render() {
          let {data} = this.props;
          return (
                <div className="friend-group">
                <dt>{data.title}</dt>
                {data.list.map((list, index) => {
                        <dd key={index}>{list}</dd>
                })}
            </div>
        );
    }
}
 
class FriendList extends React.Component {
      constructor(props) {
          super(props);
    }
 
      render() {
          let {datas} = this.props;
          return (
              <div className="friend-list">
                    {Object.keys(datas).map((key, index) => (
                    <FriendGroup data={datas[key]} key={key} />
                ))}
            </div>
        );
    }
}
2. 3. 3. 7. 1 example11

  其实没必要封装renderList函数,可直接在函数中写:

import React from 'react';
import './index.css';
 
export default class FriendList extends React.Component {
 
    render() {
        //解构数据
        let {datas} = this.props;
        console.log(datas);
 
        return(
            <div>
                <h2>好友列表</h2>
                <div className="friend-list">
                        {
                            Object.keys(datas).map( (key) => {
                                let data = datas[key];
                                return (
                                    <dl key={key} className="friend-group">
                                        <dt>{data.title}</dt>
                                    </dl>
                                )
                            } )
                        }
                </div>
            </div>
        )
    }
 
}

image-20200601142158314

参考:https://github.com/6xiaoDi/blog-react-Novice/tree/V0.211

Branch:branch1

commit description:v0.211-example11-1

tag:v0.211

  完善后面内容:继续循环json对象中的list,取值渲染组件。可再把data对象下的list数组取出,再循环取值渲染dd标签。

import React from 'react';
import './index.css';
 
export default class FriendList extends React.Component {
 
    render() {
        //解构
        let {datas} = this.props;
        console.log(datas);
 
        return(
            <div>
                <h2>好友列表</h2>
                <div className="friend-list">
                        {
                            Object.keys(datas).map( (key) => {
                                let data = datas[key];
                                let {list} = data;
                                return (
                                    <dl key={key} className="friend-group">
                                        <dt>{data.title}</dt>
                                        {
                                            list.map(item => {
                                                return <dd>{item.name}</dd>
                                            })
                                        }
                                    </dl>
                                )
                            } )
                        }
                </div>
            </div>
        )
    }
 
}

image-20200601143117870

参考:https://github.com/6xiaoDi/blog-react-Novice/tree/V0.212

Branch:branch1

commit description:v0.212-example11-2

tag:v0.212

2. 3. 3. 7. 2 example12

  其实组件传参,就类似于函数传参,可将外部数据传入,组件内部通过this.props属性拿到数据,然后内部根据数据进行渲染页面。

  上面的render有点乱,组件和函数一样,设计的时候尽量功能单一,把冗余的功能拆出去,在这里我们可以把第二层list循环拆出来封装!

src/components/friendList/group.js

import React from 'react';
 
export default class Group extends React.Component {
    render() {
        let {title, list} = this.props.data;
        return(
            <dl className="friend-group">
                <dt>{title}</dt>
                {
                    list.map( item =>
                        <dd key={item.name}>
                            {item.name}
                        </dd>
                    )
                }
            </dl>
        )
    }
 
}

src/index.js

import React from 'react';
import './index.css';
import Group from './group'
 
export default class FriendList extends React.Component {
 
    render() {
        //解构数据
        let {datas} = this.props;
        console.log(datas);
 
        return(
            <div>
                <h2>好友列表</h2>
                <div className="friend-list">
                        {
                            Object.keys(datas).map( (key) => {
                                let group = datas[key];  // 带了一个title,和list 的对象
                                return <Group key={key} data={group}></Group>
                            } )
                        }
                </div>
            </div>
        )
    }
 
}

image-20200601143117870

  如果后续group.jsrender中循环又很繁琐,为了使组件功能单一化,减少耦合,可继续往后封装组件。其实这和函数调用与封装是非常类似的!但组件比函数更好用。

参考:https://github.com/6xiaoDi/blog-react-Novice/tree/V0.22

Branch:branch1

commit description:v0.22-example12

tag:v0.22

2. 3. 4 组件交互

  一个组件除了有结构、样式,有的时候还会有交互,如上面的例子:现在我们希望当用户点击组件面板title的时候,面板组件的展开收缩状态就会发生改变

2. 3. 4. 1 绑定事件

class FriendGroup extends React.Component {
        constructor(props) {
                super(props);
          this.state = {
              expanded: false
        };
          this.expand = this.click.expand();
    }
 
      expand() {
          this.setState({
              expanded: !this.state.expanded
        });
    }
 
      render() {
          let {data} = this.props;
          let {expanded} = this.state;
          return (
                <div className={[
                "friend-group",
                expanded && "expanded"
            ].join(' ')}>
                <dt onClick={this.expand}>{data.title}</dt>
                {data.list.map((list, index) => {
                        <dd key={index}>{list}</dd>
                })}
            </div>
        );
    }
}

React.js 的事件绑定需要注意:

  • 事件名称是驼峰命名的

  • 事件绑定函数的 this 指向

    • 通过 bind 改变 this 指向,为了能够在方法中调用组件对象的属性和其它方法,我们需要把 this 指向组件

    • 通过箭头函数处理 this

2. 3. 4. 2 获取事件对象

  事件绑定函数的第一个参数是事件对象,通过事件对象可以获取到原生 DOM 对象

2. 3. 4. 2. 1 example13

  需求:展开和收缩面板

  把样式中的.friend-groupdisplay: none;注释打开。

src/components/friendList/index.css

.friend-list {
    border: 1px solid #000000;
    width: 200px;
}
.friend-group {
    margin: 0;
}
.friend-group dt {
    padding: 10px;
    background-color: rgb(64, 158, 255);
    font-weight: bold;
}
.friend-group dd {
    padding: 10px;
    display: none;
}
.friend-group.expanded dd {
    display: block;
}
.friend-group dd.checked {
    background: green;
}

  我们点击是需要增加事件的。

  注意:React的JSX的标签中增加的事件,命名风格:onXxxx,采用on后面单词小驼峰命名。

src/components/friendList/group.js

    render() {
        let {title, list} = this.props.data;
        return(
            <dl className="friend-group">
                <dt onClick={()=>{console.log("你点到我了!")}}>{title}</dt>
                {
                    list.map( item =>
                        <dd key={item.name}>
                            {item.name}
                        </dd>
                    )
                }
            </dl>
        )

  但是这样写肯定不好,如果函数逻辑复杂,这些写会很恶心,我们可以把函数拿到外面去!

参考:https://github.com/6xiaoDi/blog-react-Novice/tree/v0.23-1

Branch:branch1

commit description:v0.23-1-example13-1 (展开和收缩面板:React的JSX的标签中增加的事件)

tag:v0.23-1

src/components/friendList/group.js

import React from 'react';
 
export default class Group extends React.Component {
 
    toggle() {
        console.log('你点到我了吗')
    }
 
    render() {
        let {title, list} = this.props.data;
        return(
            <dl className="friend-group">
                <dt onClick={this.toggle}>{title}</dt>
                {
                    list.map( item =>
                        <dd key={item.name}>
                            {item.name}
                        </dd>
                    )
                }
            </dl>
        )
 
    }
 
}

参考:https://github.com/6xiaoDi/blog-react-Novice/tree/v0.23-2

Branch:branch1

commit description:v0.23-2-example13-2(展开和收缩面板:React的JSX的标签中增加的事件,优化函数在标签外部)

tag:v0.23-2

  我们在看一下事件中的this

    toggle() {
        console.log(this);
    }

  事件函数中this无非两种情况:1、当前元素(dt标签) 2、当前所对应的事件对象,但实际却是undefined

  React默认情况下,事件绑定的函数中this值为undefined,我们需要根据实际使用的情况来手动处理(可以在调用的时候,利用bind方法绑定this)。

  为什么是这样呢?

  因为这个方法可以被事件调用,也可以在类的方法中用this调用等,有很多种形式,所以React就直接默认this为空从而避免发生奇怪问题,然后由开发人员自己确定。

参考:https://github.com/6xiaoDi/blog-react-Novice/tree/v0.23-3

Branch:branch1

commit description:v0.23-3-example13-3(展开和收缩面板:查看React的JSX的标签中事件函数this指向)

tag:v0.23-3

  可以在调用的时候,利用bind方法绑定this

  事件对象:事件函数的第一个参数就是事件对象(是被React处理过的对象,不是原生对象,原生在它的nativeEvent属性中,但是使用方式和原生是一样的。这个很像jq中的二次事件对象,即处理兼容后的事件对象)

image-20200702160523039

import React from 'react';
 
export default class Group extends React.Component {
    toggle(e) {
        console.log(this);
        console.log(e.target);
    }
 
    render() {
        let {title, list} = this.props.data;
        return(
            <dl className="friend-group">
                <dt onClick={this.toggle.bind(this)}>{title}</dt>
                {
                    list.map( item =>
                        <dd key={item.name}>
                            {item.name}
                        </dd>
                    )
                }
            </dl>
        )
    }
 
}

参考:https://github.com/6xiaoDi/blog-react-Novice/tree/v0.23-4

Branch:branch1

commit description:v0.23-4-example13-4(展开和收缩面板:指定React的JSX的标签中事件函数this指向,查看事件对象)

tag:v0.23-4

  这里暂时未实现面板手收缩,我们放在下一个实例中。

2. 3. 4. 3 react组件中的事件小结

  • 事件是通过 jsx 标签上的行间事件属性来绑定函数的
    • 事件名称是使用小驼峰命名:onMouseOver
    • 事件绑定函数可以直接写在行间,也可以绑定到类中其他方法中
  • 事件中this
    • 默认情况下,事件绑定的函数中this值为undefined,我们需要根据实际使用的情况来手动处理(可以在调用的时候,利用bind方法绑定this)
  • 事件对象
    • 事件函数的第一个参数就是事件对象(是被React处理过的对象,不是原生对象,原生在它的nativeEvent属性中,但是使用方式和原生是一样的。这个很像jq中的二次事件对象,即处理兼容后的事件对象)

2. 3. 5 组件状态

  有的时候,一个组件除了可以接收外部传入的数据,还会有属于自己的内部私有数据,同时组件私有数据的更新还会导致组件的重新渲染,比如上面的例子中的每一个好友面板会有一个内部私有数据来控制面板的展开与收缩,我们又把这种数据称为组件状态

2. 3. 5. 1 state

  存储组件状态的对象

class FriendGroup extends React.Component {
        constructor(props) {
                super(props);
          this.state = {
              expanded: false
        };
    }
 
      render() {
          let {data} = this.props;
          let {expanded} = this.state;
          return (
                <div className={[
                "friend-group",
                expanded && "expanded"
            ].join(' ')}>
                <dt>{data.title}</dt>
                {data.list.map((list, index) => {
                        <dd key={index}>{list}</dd>
                })}
            </div>
        );
    }
}
2. 3. 5. 1. 1 example14

  有的时候,一个组件除了可以接收外部传入的数据,还会有属于自己的内部私有数据,同时组件私有数据的更新还会导致组件的重新渲染,比如上面的例子中的每一个好友面板会有一个内部私有数据来控制面板的展开与收缩,我们又把这种数据称为组件状态

  因此组件有两种状态分为外部状态和内部状态,一个外部传入(props),一个是内部私有(state),两者互不干扰。当然有的时候,可以将props对象的某个值作为state的初始值去使用。

  state在父类React.Component就进行了定义,我们需要做的事情就是初始赋值就行了。state 的初始化工作需要在构造函数中进行,构造函数其实就是组件最开始执行的一个函数。

  注意:在构造函数中养成习惯,将类中方法绑定this指针。

  收缩功能实现,小迪写的样式中的.friend-group.expanded dd,如果className中存在则展开,否则收缩。我们可以采取数组形式,放多个className,最后会自动(调用toString)帮我们转字符串。

app\src\components\friendList\group.js

import React from 'react';
 
export default class Group extends React.Component {
 
    toggle(e) {
        console.log(this);
        console.log(e.target);
    }
 
    render() {
        let {title, list} = this.props.data;
       
        return(
            <dl className={['friend-group','expanded']}>
                <dt onClick={this.toggle}>{title}</dt>
                {
                    list.map( item => <dd key={item.name}>{item.name}</dd> )
                }
            </dl>
        )
 
    }
 
}

  类名多了逗号!

image-20200702163141489

参考:https://github.com/6xiaoDi/blog-react-Novice/tree/v0.24-1

Branch:branch1

commit description:v0.24-1-example14-1 (展开和收缩面板:样式丢失问题)

tag:v0.24-1

  发现有逗号了,样式才丢失,需要join采取空格分隔即可

import React from "react";

export default class Group extends React.Component {

    toggle(e) {
        console.log(this);
        console.log(e.target);
    }

    render() {
        let {title, list} = this.props.data;
        return (
            <dl className={['friend-group','expanded'].join(' ')}>
                <dt onClick={this.toggle.bind(this)}>{title}</dt>
                {
                    list.map( item =>
                        <dd key={item.name}>
                            {item.name}
                        </dd>
                    )
                }
            </dl>
        )
    }

}

image-20200702163544232

参考:https://github.com/6xiaoDi/blog-react-Novice/tree/v0.24-2

Branch:branch1

commit description:v0.24-2-example14-2 (展开和收缩面板:解决样式丢失问题)

tag:v0.24-2

  我们采用三目运算符,动态变化。

import React from "react";

export default class Group extends React.Component {

    constructor(props) {
        super(props);

        this.state = {
            expanded: false  // 展开收缩状态,默认收缩
        }
        // 一般都在构造函数中绑定好this
        this.toggle = this.toggle.bind(this);
    }

    toggle(e) {
        this.state.expanded = !this.state.expanded;
        console.log(this.state.expanded);
    }

    render() {
        let {title, list} = this.props.data;
        let {expanded} = this.state;
        
        return (
            <dl className={['friend-group', expanded ? 'expanded' : ''].join(' ')}>
                <dt onClick={this.toggle.bind(this)}>{title}</dt>
                {
                    list.map( item =>
                        <dd key={item.name}>
                            {item.name}
                        </dd>
                    )
                }
            </dl>
        )
    }

}

  点击有反应,但没有任何效果,这是啥情况呢?

  实际上state 中的值不能直接去改变,react 中并没有主动的去监听拦截数据的变化,如果直接去修改state中的值是不会重新渲染视图的。但它提供了方法:setState来修改state中的值,且在方法中去调用render。

参考:https://github.com/6xiaoDi/blog-react-Novice/tree/v0.24-3

Branch:branch1

commit description:v0.24-3-example14-3 (展开和收缩面板:切换面板无响应!)

tag:v0.24-3

2. 3. 6 状态更新

  当我们希望更新组件状态的时候,不要直接修改 state 的值,而是需要调用 setState 方法来进行更新

2. 3. 6. 1 setState 方法

  setState 方法由父类 React.Component 提供,当该方法被调用的时候,组件的 state 会被更新,同时会重新调用组件的 render 方法对组件进行渲染

不要直接修改 state,而是调用 setState 方法来更新,组件构造函数是唯一可以对 state 直接赋值(初始化)的位置

this.setState({key:val})
2. 3. 6. 1. 1 example15

  React的数据拦截原理,原生js实现:通过函数才可触发

let obj = {
    x: 1
};
 
function setVal(val) {
    obj.x = val;
    console.log('修改了值');
}
 
setVal(100);

image-20200702175921228

参考:https://github.com/6xiaoDi/blog-react-Novice/tree/v0.25-1

Branch:branch1

commit description:v0.25-1-example15-1 (展开和收缩面板:React的数据拦截原理)

tag:v0.25-1

  Vue的数据拦截原理,原生js通过defineProperty实现:通过属性操作即可触发

let obj = {
    x: 1
};
 
Object.defineProperty(obj, 'x', {
    set() {
        console.log('你修改了值')
    }
});
obj.x = 10;

image-20200702180241437

参考:https://github.com/6xiaoDi/blog-react-Novice/tree/v0.25-2

Branch:branch1

commit description:v0.25-2-example15-2(展开和收缩面板:Vue的数据拦截原理)

tag:v0.25-2

  React的setState函数

  内部采取(当然还有其他特性,暂时我们先了解这么多):Object.assign(this.state, {expanded: !this.state.expanded})

  Object.assign()方法用于将所有可枚举属性的值从一个或多个源对象复制到目标对象。它将返回目标对象。即对象覆盖合并。

import React from "react";

export default class Group extends React.Component {

    constructor(props) {
        super(props);

        this.state = {
            expanded: false  // 展开收缩状态,默认收缩
        }
        // 一般都在构造函数中绑定好this
        this.toggle = this.toggle.bind(this);
    }

    toggle(e) {
        this.setState({
             expanded: !this.state.expanded
        });
    }

    render() {
        console.log('render')
        let {title, list} = this.props.data;
        let {expanded} = this.state;

        return (
            <dl className={['friend-group', expanded ? 'expanded' : ''].join(' ')}>
                <dt onClick={this.toggle}>{title}</dt>
                {
                    list.map( item =>
                        <dd key={item.name}>
                            {item.name}
                        </dd>
                    )
                }
            </dl>
        )
    }

}

  我们发现setState会自动调用render方法。因此我们只需关注,什么时候修改state就调用setState方法,如何渲染,就在render方法实现即可。不用关心如何改变state后实时渲染的问题了。

参考:https://github.com/6xiaoDi/blog-react-Novice/tree/v0.25-3

Branch:branch1

commit description:v0.25-3-example15-3(展开和收缩面板最终实现)

tag:v0.25-3

2. 3. 6. 1. 2 更新异步

  出于性能考虑,setState 方法的修改并不是立即生效的

// this.state.val = 0
this.setState({
      val: this.state.val + 1
});
// this.state.val 的值并不是 1
console.log(this.state.val);
2. 3. 6. 1. 2. 1 example16
import React from "react";

export default class Group extends React.Component {

    constructor(props) {
        super(props);

        this.state = {
            expanded: false  // 展开收缩状态,默认收缩
        }
        // 一般都在构造函数中绑定好this
        this.toggle = this.toggle.bind(this);
    }

    toggle(e) {
        this.setState({
             expanded: !this.state.expanded
        });
        // 不是立即生效的
        console.log(this.state.expanded);
    }

    render() {
        console.log('render')
        let {title, list} = this.props.data;
        let {expanded} = this.state;

        return (
            <dl className={['friend-group', expanded ? 'expanded' : ''].join(' ')}>
                <dt onClick={this.toggle}>{title}</dt>
                {
                    list.map( item =>
                        <dd key={item.name}>
                            {item.name}
                        </dd>
                    )
                }
            </dl>
        )
    }

}

  出于性能考虑,setState 方法的修改并不是立即生效的。和浏览器渲染概念类似,并不是一操作完dom,就会生效一样(刷新页面),否则性能会非常差。它会搜集更新,等所有任务完成后,再统一最后处理。因此这个是个坑,你一设置完就去获取肯定不是我们想要的值。

参考:https://github.com/6xiaoDi/blog-react-Novice/tree/v0.26

Branch:branch1

commit description:v0.26-example16(state更新异步问题)

tag:v0.26

2. 3. 6. 1. 3 更新合并

  React 会把多个 setState 合并成一个调用

// this.state.val = 0
this.setState({
      val: this.state.val + 1
});
this.setState({    // 因为异步的问题,this.state.val 的值在这里还是0
      val: this.state.val + 1
});

  上述代码原理其实就是如下代码:

Object.assign(
  previousState,
  {val: state.val + 1},
  {val: state.val + 1},
  ...
)

  相当于:

Object.assign(
  this.setState,
  {val: 1},
  {val: 1},
  ...
)
2. 3. 6. 1. 4 回调函数(修改state的另一种形式)

  如果,有多个 setState 的调用,后一次的修改如果需要依赖上一次修改的值,那么可以使用函数的方式(解决上面的坑),函数不是执行完毕后传入assign,而是把整个函数传入,然后在内部执行,最终函数的返回值作为合并的对象。因为函数可以依次执行,执行1次改一次,则第一次执行是1,第二次执行的结果是2。

// this.state.val = 0
this.setState((state, props) => {
      return {val: state.val + 1}
});
this.setState((state, props) => {
      return {val: state.val + 1}
});

2. 3. 7 props 与 state 的区别

  state 的主要作用是用于组件保存、控制、修改自己的可变状态(私有状态),在组件内部进行初始化,也可以在组件内部进行修改,但是组件外部不能修改组件的 state(与外界无关,但外界可以初始化state

  props 的主要作用是让使用该组件的父组件可以传入参数来配置该组件,它是外部传进来的配置参数,组件内部无法控制也无法修改

  stateprops 都可以决定组件的外观和显示状态。通常,props 做为不变数据或者初始化数据传递给组件,可变状态使用 state

  能使用 props 就不要使用 state(官方建议),因为如果一个组件内部私有状态太多了,它可能就对外部造成很大困扰,而外部使用者并不知道内部发生了什么,即外部不可控。其实就类似vue,内部语法糖太多了,我们外部仅仅会操作,但不知道内部做了什么,发生了奇怪的问题,不看源码,根本解决不了问题。

2. 3. 7. 1 无状态组件

  没有 state 的组件,我们称为无状态组件,因为状态会带来复杂性,所以,通常我们推荐使用无状态组件,也鼓励编写无状态组建。

2. 3. 7. 2 函数式组件

  函数式组件没有 state (现在的函数式组件有state了,如:react hooks),所以通常我们编写使用函数式组件来编写无状态组件。

2. 3. 8 组件通信与数据流

  在上面的案例中,我们实现了一个多选式的好友列表(每个独立的面板都有自己独立的状态,互不影响)。下面我们再来看另外一种情况:单选式的好友列表(多组列表同时只能有一个为展开状态,也就是多个面板之间的状态是互相影响的)

2. 3. 8. 1 状态提升(将子组件提升到父组件)

  通过分析可以发现,面板的展开状态不在是组件内部私有状态了,多个组件都会受到这个状态的影响,也就是它们共享了一个状态。为了能够让多个不同组件共享同一个状态,这个时候,我们把这个状态进行提升,交给这些组件最近的公共父组件进行管理

class FriendGroup extends React.Component {
        constructor(props) {
                super(props);
 
          this.changeExpand = this.changeExpand.bind(this);
    }
 
      changeExpand() {
          typeof this.props.onExpand === 'function' &&             this.props.onExpand(this.props.index);
    }
 
      render() {
          let {data, expandedIndex, index} = this.props;
          return (
                <div onClick={this.changeExpand} className={[
                "friend-group",
                expandedIndex === index && "expanded"
            ].join(' ')}>
                <dt>{data.title}</dt>
                {data.list.map((list, index) => {
                        <dd key={index}>{list}</dd>
                })}
            </div>
        );
    }
}
 
class FriendList extends React.Component {
      constructor(props) {
          super(props);
 
          this.state = {
              expandedIndex: 0
        }
 
          this.changeExpandedIndex = this.changeExpandedIndex.bind(this);
    }
 
      changeExpandedIndex(expandedIndex) {
        this.setState({
            expandedIndex
        });
    }
 
      render() {
          let {datas} = this.props;
          return (
              <div onExpand={this.changeExpandedIndex} className="friend-list">
                    {Object.keys(datas).map((key, index) => (
                    <FriendGroup data={datas[key]} index={index} expandedIndex={expandedIndex} key={key} />
                ))}
            </div>
        );
    }
}

2. 3. 8. 2 数据流

  在 React.js 中,数据是从上自下流动(传递)的,也就是一个父组件可以把它的 state / props 通过 props 传递给它的子组件,但是子组件不能修改 props - React.js 是单向数据流,如果子组件需要修改父组件状态(数据),是通过回调函数方式来完成的。

2. 3. 8. 2. 1 props 函数

  父组件在调用子组件的时候,传入一个函数类型的 props

<FriendGroup data={datas[key]} index={index} expandedIndex={expandedIndex} key={key} />

  在子组件中根据具体的处理逻辑,调用父组件通过 props 传入的对应函数,类似 on 事件 的机制,同时子组件可以在调用回调函数的时候,传入一些数据

2. 3. 8. 2. 2 更新父组件

  在父组件的代码中,通过 setState 重新渲染父组件,父组件的更新会重新渲染子组件

  注意:以下这种是嵌套的父子关系,我们今天所讲的是在其内部属性里存着(而不是嵌套父子关系),即普通的父子关系。

<FriendList>
	<group/>
</FriendList>
2. 3. 8. 2. 2. 1 example17

  换需求:点击选项,同时只能有一个是展开的状态。

  分析后得出此时的状态不是私有状态了(group组件中),因为别人也需要知道!这个状态只能交给其父级(group.js上级是index.js)去维护了。

src/App.js

import React from 'react';
import Custom from "./components/custom";
import Custom02 from "./components/custom02";
import FriendList from "./components/friendList2";

let datas = {
    family: {
        title: '家人',
        list: [
            {name: '爸爸'},
            {name: '妈妈'}
        ]
    },
    friend: {
        title: '朋友',
        list: [
            {name: '张三'},
            {name: '李四'},
            {name: '王五'}
        ]
    },
    customer: {
        title: '客户',
        list: [
            {name: '阿里'},
            {name: '腾讯'},
            {name: '头条'}
        ]
    }
};

function App() {
  return (
    <div className="App">
        <h1>CSDN</h1>
        <hr />
        {/*{Custom()}*/}
        {/*<Custom datas={datas} v={1} k={'kkk'}/>*/}
        {/*<Custom02 />*/}
        <FriendList datas={datas}/>
    </div>
  );
}

export default App;

app\src\components\friendList2\index.js

import React from 'react';
import './index.css';
import Group from "./group";

export default class FriendList extends React.Component {

    constructor(props) {
        super(props);

        // 状态
        let len = Object.keys(this.props.datas).length;
        this.state = {
            expanded: new Array(len).fill(false)   // 默认三个导航都是收缩状态
        };
    }

    render() {
        //解构数据
        let {datas} = this.props;
        let {expanded} = this.state;

        return(
            <div>
                <h2>好友列表</h2>
                <div className="friend-list">
                    {Object.keys(datas).map( (key, index) => {
                            let group = datas[key]; // 带了一个title,和list 的对象
                            return <Group key={key} expanded={expanded[index]} data={group}></Group>
                        }
                    )}
                </div>
            </div>
        )
    }

}

app\src\components\friendList2\group.js

import React from "react";

export default class Group extends React.Component {

    constructor(props) {
        super(props);

        this.toggle = this.toggle.bind(this);
    }

    toggle(e) {
    }

    render() {
        let {expanded, data: {title, list}} = this.props;

        return (
            <dl className={['friend-group', expanded ? 'expanded' : ''].join(' ')}>
                <dt onClick={this.toggle}>{title}</dt>
                {
                    list.map( item =>
                        <dd key={item.name}>
                            {item.name}
                        </dd>
                    )
                }
            </dl>
        )
    }

}

image-20200601175918368

参考:https://github.com/6xiaoDi/blog-react-Novice/tree/v0.27-1

Branch:branch1

commit description:v0.27-1-example17-1 (点击选项,同时只能有一个是展开的状态:父级默认三个导航都是收缩状态)

tag:v0.27-1

  测试一下都设置成true

src/components/friendList2/index.js

constructor(props) {
        super(props);
 
        // 状态
        let len = Object.keys(this.props.datas).length;
        this.state = {
            expanded: new Array(len).fill(true)
        };
    }

image-20200601180041229

参考:https://github.com/6xiaoDi/blog-react-Novice/tree/v0.27-2

Branch:branch1

commit description:v0.27-2-example17-2 (点击选项,同时只能有一个是展开的状态:测试父级默认三个导航都是张开状态)

tag:v0.27-2

app\src\components\friendList2\group.js

import React from "react";

export default class Group extends React.Component {

    constructor(props) {
        super(props);

        this.toggle = this.toggle.bind(this);
    }

    toggle(e) {
        // 子组件内部不允许直接修改父级传入的props
        this.props.expanded = !this.props.expanded;
    }

    render() {
        let {expanded, data: {title, list}} = this.props;

        return (
            <dl className={['friend-group', expanded ? 'expanded' : ''].join(' ')}>
                <dt onClick={this.toggle}>{title}</dt>
                {
                    list.map( item =>
                        <dd key={item.name}>
                            {item.name}
                        </dd>
                    )
                }
            </dl>
        )
    }

}

  这样子操作会报错,因为子组件内部不允许直接修改父级传入的props。如果可以修改,会导致其他组件用这个数据时出现问题。可在父级中加入事件回调进行修改(就类似士兵执行任务时要实时给领导打报告,不能擅自做决定,这就是规定!这样就对数据有了安全保障,避免程序出错。数据由谁维护,修改权就由谁管理。)

image-20200702204802620

参考:https://github.com/6xiaoDi/blog-react-Novice/tree/v0.27-3

Branch:branch1

commit description:v0.27-3-example17-3 (点击选项,同时只能有一个是展开的状态:子级修改父级传入props报错)

tag:v0.27-3

app\src\components\friendList2\index.js

import React from 'react';
import './index.css';
import Group from "./group";

export default class FriendList extends React.Component {

    constructor(props) {
        super(props);

        // 状态
        let len = Object.keys(this.props.datas).length;
        this.state = {
            expanded: new Array(len).fill(false)   // 默认三个导航都是收缩状态
        };

        this.changeExpanded = this.changeExpanded.bind(this);
    }

    // 传给子级的事件回调函数:修改子组件的展开状态
    // index 点击第几个导航栏
    changeExpanded(index, val) {
        let expanded = this.state.expanded;
        expanded.fill(false);
        expanded[index] = val;
        this.setState({
            expanded
        });
    }

    render() {
        //解构数据
        let {datas} = this.props;
        let {expanded} = this.state;

        return(
            <div>
                <h2>好友列表</h2>
                <div className="friend-list">
                    {Object.keys(datas).map( (key, index) => {
                            let group = datas[key]; // 带了一个title,和list 的对象
                            return <Group key={key} index={index} onChange={this.changeExpanded} expanded={expanded[index]} data={group}></Group>
                        }
                    )}
                </div>
            </div>
        )
    }

}

app\src\components\friendList2\group.js

import React from "react";

export default class Group extends React.Component {

    constructor(props) {
        super(props);

        this.toggle = this.toggle.bind(this);
    }

    toggle(e) {
        // 子组件内部不允许直接修改父级传入的props
        // this.props.expanded = !this.props.expanded;
        if ('function' === typeof this.props.onChange) {
            // 这个函数this指向的是父级的组件this
            this.props.onChange(this.props.index, !this.props.expanded);
        }
    }

    render() {
        let {expanded, data: {title, list}} = this.props;

        return (
            <dl className={['friend-group', expanded ? 'expanded' : ''].join(' ')}>
                <dt onClick={this.toggle}>{title}</dt>
                {
                    list.map( item =>
                        <dd key={item.name}>
                            {item.name}
                        </dd>
                    )
                }
            </dl>
        )
    }

}

参考:https://github.com/6xiaoDi/blog-react-Novice/tree/v0.27-4

Branch:branch1

commit description:v0.27-4-example17-4 (点击选项,同时只能有一个是展开的状态:最终实现)

tag:v0.27-4

2. 4 key(上节演示过了!)

class MyComponent extends React.Component {
 
    constructor(props) {
        super(props);
 
        this.state = {
            arr: ['html', 'css', 'javascript']
        }
        this.sort = this.sort.bind(this);
    }
 
    sort() {
        this.setState({
            arr: this.state.arr.sort( _ => Math.random() - .5 )
        });
    }
 
    render() {
        let {arr} = this.state;
        return(
            <div>
                <ul>
                    {arr.map( (v, k) => (
                        <li>
                            <input type="checkbox"/>
                            <span>{v}</span>
                        </li>
                    ) )}
                </ul>
                <button onClick={this.sort}>乱序</button>
            </div>
        );
    }
}
 
ReactDOM.render(
    <MyComponent />,
    document.getElementById('app')
);

通过控制台查看结构变化

2. 4. 1 渲染优化

  1. 用JS对象模拟DOM树 : Virtual DOM
  2. 通过 DOM 操作Virtual DOM 生成真实的 DOM
  3. 后期的更新,会比较 新老 Virtual DOM ,通过 diff 算法优化更新操作

2. 4. 1. 1 Virtual DOM

  原生 DOM 对象结构太复杂,同时存在许多情况下用不到的一些特性

  Virtual DOM 本质上与 原生 DOM 没有区别,都是 JavaScript 对象,只是它的结构更加简洁

2. 4. 1. 2 Diffing 算法

  实际Diffing 算法只要用到虚拟dom的框架,都有diff算法。

  Web界面由DOM树来构成,当其中某一部分发生变化时,其实就是对应的某个DOM节点发生了变化。在React中,构建UI界面的思路是由当前状态决定界面。前后两个状态就对应两套界面,然后由React来比较两个界面的区别,这就需要对DOM树进行Diff算法分析。

  传统算法:找到两棵树任意的树之间最小的修改是一个复杂度为O(n³)的问题,因为需要不同的层级。

react用了一种简单但是强大的技巧,达到了接近O(n)的复杂度。把树按照层级分解,只比较对应层级的节点,进行对比。

  vue和react的虚拟dom都采用类似的diff算法,核心大概可以归为两点:

  1. 两个相同的组件产生类似的DOM结构,不同的组件产生不同的DOM结构;

  2. 同一层级的一组节点,他们可以通过唯一的id进行区分。

基于以上两点,使得虚拟DOMDiff算法的复杂程度从O(n³)降到了O(n)

img

  列表比较,写一个key属性帮助React来处理它们之间的对应关系,实际中,在子元素中找到唯一的key通常很容易。如希望插入一个节点,传统方法则是类似数组插入一样,在中间插入一个值,后面的值逐个往后便宜,这样效率低下。所以我们需要使用key来给每个节点做一个唯一的标识,Diff算法就可以正确的识别此节点,找到正确的位置区插入新的节点,这样就避免了没有意义的节点刷新了。

image-20200702195257811

  Components比较,React app通常由用户定义的component组合而成。通常结果是一个主要是很多div组成的树。

  这个信息也被React的diff算法考虑进去了,React只会匹配相同类型(ES6 class)的component

image-20200702195931439

  合并操作,当调用componentsetstate方法的时候,React将其标记为dirty。 到每一个事件循环结束,React检查所有标记 dirtycomponent重新绘制。这就不会每次执行setstate方法的时候都发生渲染了,这样效率会极其低下了。

image-20200702200744163

  正常情形下,假如红色节点发生改变,其下面的所有子节点都得重新刷新,但假如子节点没有变化就没必要刷新,因此这样效率极其低下。假设只判断出蓝色部分需要刷新,则对应去重新渲染。

  选择性子树渲染,在组件上实现下面的生命周期方法

boolean shouldComponentUpdate(object nextProps, object nextState)

  根据 component的上面这个生命周期(后面再说生命周期),将前一个和下一个props/state,进行比较,来判断子节点是否变化。这就告诉 React这个 component有没有更新,需不需要重新绘制。实现得好的话,可以带来巨大的性能提升。

image-20200702200802025



(后续待补充)
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章