最新公告
  • 欢迎您光临起源地模板网,本站秉承服务宗旨 履行“站长”责任,销售只是起点 服务永无止境!立即加入钻石VIP
  • React快速暴力入门

    正文概述 掘金(wanted)   2021-05-10   497

    React快速暴力入门

    React 作为如今三大框架之一,在进行了短期的学习后,整理了一些笔记,做一下分享: (新人一个,多多包涵)

    一、React简介

    1. 什么是React:

    2. React的特点

    1. 声明式设计 −React的每个组件都是通过声明创建,使得页面逻辑更加清晰

    React快速暴力入门 2. 虚拟DOM −React每次渲染页面时会创建一个虚拟DOM,与现有的DOM进行对比,有差异的才进行替换重新渲染,提高了效率。 React快速暴力入门 3. JSX − JSXJavaScript 语法的扩展。React 开发不一定使用 JSX ,但我们建议使用它。

    // 在 javascript中创建元素
    const DOM = document.createElement("h1"); // 真实DOM
    DOM.innerText = "这是h1标签";
    
    // 在 jsx中创建元素
    const VDOM = <h1>这是h1标签</h1> // 虚拟DOM
    
    1. 组件 − 通过 React 构建组件,使得代码更加容易得到复用,能够很好的应用在大项目的开发中。
    // 不必现在就看懂,仅需要知道每个组件都是需要进行声明后才能使用
    import React, {PureCompoent} from "react";
    export default class Header extends PureCompoent{
        render(){
            return <header>这是头部组件</header>
        }
    }
    // ------------------------------------------------
    // 在需要使用 header组件时调用即可重复使用
    import Header from "./Header";
    <Header></Header>
    
    1. 单向响应的数据流 − React 实现了单向响应的数据流,从而减少了重复代码,这也是它为什么比传统数据绑定更简单。(这个在后面会了解到)

    3. 安装与使用

    1. 使用cdn
    <script crossorigin src="https://unpkg.com/react@16/umd/react.development.js"></script>
    <script crossorigin src="https://unpkg.com/react-dom@16/umd/react-dom.development.js"></script>
    
    1. 通过下载导入 React包
    <script src="../js/react.development.js"></script>
    <script src="../js/react-dom.development.js"></script>
    <script src="../js/babel.min.js"></script>
    
    1. 使用 react的脚手架create-react-app 创建项目文件会自带react
    npm i create-react-app -g
    create-react-app 项目名称
    

    4. 虚拟DOM

    (1) 关于虚拟DOM

    虚拟DOM的本质就是一个对象,真实DOM也是个对象,但虚拟DOM的属性更少,更加轻量

    // 虚拟DOM  
    const VDOM = <h1>Hello World</h1>; // 此处不需要引号,因为不是字符串
    // 真实DOM
    const TDOM = document.querySelector("#app");
    
    console.log("虚拟DOM: ", VDOM);   // Object
    console.log("真实DOM: ", TDOM);   // <div id="app"></div>
    console.log("虚拟DOM类型: ", typeof VDOM);  // object
    console.log("真实DOM类型: ", typeof TDOM);  // object
    console.log(VDOM instanceof Object);    // true
    console.log(TDOM instanceof Object);    // true
    

    (2) 创建虚拟DOM

    1. 通过 React的方法 createElement()方法创建虚拟DOM
    // React.createElement(标签名称, 标签属性, 标签内容)
    const VDOM1 = React.createElement("h1", {id: "title"}, "This is Title");
    
    1. 使用方法一的语法糖创建虚拟DOM
    const VDOM2 = <h1 id="title">This is Title</h1>;
    

    5. 关于JSX

    jsx语法与javascript语法非常相似,只有一些需要注意的地方

    // 虚拟DOM  
    const VDOM = (  // 使用括号框住 jsx标签表示层级会更加美观些。
        <h1>
            <span>Hello World</span>
        </h1>
    )
    const myid = "HeLlO";
    const content = "Happy New Year"
    const students = [
    {id: "001", name: "Tom", age: 18},
    {id: "002", name: "Tim", age: 19},
    {id: "003", name: "Jerry", age: 20},
    ];
    const VDOM2 = (
        <div>
            <h2 className="title" id={myid.toLowerCase()}>
                <span style={{color: 'pink'}}>{content}</span>
            </h2>
            <ul>{/* 使用 ES6的 map() 函数进行列表渲染(将数据批量渲染到页面上) */}
            {
                students.map(student=>{
                    return <li key={student.id}>{student.name}---{student.age}</li>
                }
            }
            </ul>
            <input type="text"/>    
        </div>
    )
    

    jsx语法规则:

    1. 定义虚拟DOM时不要写引号
    2. 标签中混入 js表达式时要用 {}
      • 表达式会产生一个值,可以放在任何需要值的地方
      • 语句时一串代码,用于处理逻辑用的
    3. 标签中类名指定不用 class,要用 className
    4. 标签中使用内联样式时,要用双括号写法(使用小驼峰写法写css样式)
    5. 添加事件属性时(如onclick),on后面的单词首字母要大写(如onClick
    6. 虚拟DOM必须只有一个根标签
    7. 标签必须闭合
    8. 标签开头
      • 标签开头为小写时,会被 jsx编译为 html标签,若 html没有对应同名元素则报错
      • 标签开头为大写时,会被 jsx识别为组件,若没找到对应组件则

    6. 渲染到页面上

    使用 ReactDOMrender()方法进行渲染

    const VDOM = <h1 id="title">This is a Title</h1>;    // 创建虚拟DOM
    // ReactDOM.render( 组件(虚拟DOM),  要绑定到哪个元素上 );
    ReactDOM.render(VDOM, document.querySelector("#root"));
    

    二、组件的使用

    React中,有两种组件的创建方式,分别为函数式组件类式组件。其中 类式组件使用频率较高(React 16.8出现 Hook后函数式组件也多了起来)

    函数式组件

    顾名思义,该组件是由函数来写的

    function Demo(props){    // 定义一个组件,名为 Demo
        return <h2>This is a component</h2>;    // 返回值为组件的DOM内容
    }
    ReactDOM.render(<Demo/>, document.querySelector("#root"))
    

    函数式组件定义:

    • 函数名称必须大写
    • 调用时以标签方式调用且开头大写
    • 函数式组件的参数为 props(后面会讲)

    类式组件

    该组件通过类来构建,但需要继承React自带的一个类Component

    // 使用 ES6写法创建类式组件,并继承于 React.Component
    class Demo extends React.Component{
        
        // 添加render() 函数(必须),返回值为组件的虚拟DOM
        render(){
            console.log(this);      // render() 函数的 this 指向组件的实例对象
            return <h1>This is a Title!</h1>
        }
    }
    ReactDOM.render(<Demo/>, document.querySelector("#root"));
    

    类式组件的定义:

    • 必须继承 React的内置类Component
    • 必须包含方法 render()
    • 构造函数 constructor()的参数为 props(后面会讲),如果需要使用constructor则必须调用父类的构造函数 super(props)

    类式组件挂载时的执行情况:

    1. React解析组件标签,发现了 Demo组件
    2. 发现为类式组件,随后 new出该类的实例对象,通过实例调用原型对象上的render方法
    3. render方法返回的虚拟DOM转为真实DOM,随后呈现到页面中

    组件定义的注意事项:

    1. 类式组件的render() 返回的组件标签与函数式组件返回的组件标签一定要有一个根标签
    2. 都必须以大写字母开头

    组件的挂载与卸载

    挂载已经看了很多个了,直接上代码:

    // ReactDOM.render( 组件, 要绑定在哪个元素上 );
    ReactDOM.render( <Demo/>, document.querySelector("#app") );
    

    卸载的代码长点,我也直接放上来了:

    // ReactDOM.unmountComponentAtNode( 要卸载哪个元素上的组件 );
    ReactDOM.unmountComponentAtNode( document.querySelector("#app") );
    

    其他小知识

    1. 包含表单元素的组件分为非受控租价受控组件
      • 受控组件:表单组件的输入组件随着输入并将内容存储到状态中(随时更新)
      • 非受控组件:表单组件的输入组件的内容在有需求的时候才存储到状态中(即用即取)

    三、组件的三大属性

    组件的实质就是个对象,而对象自然有属性,在组件里最常用的三个属性分别是 statepropsrefs

    1. state

    state即组件的状态,说的明白点就是该组件所存储的(所需要使用的)数据

    类式组件中的使用:

    class Weather extends React.Component{
        constructor(props){
            super(props);
            // this.state = {weather: "Spring"}     // 也可以在构造函数中定义 state
        }
        
        state = {   // 定义 state
            weather: "summer",
        }
        
        render(){
            // 当前季节为:summer
            return <h2>当前季节为:{ this.state.weather }</h2>
        }
    }
    

    使用的时候通过 this.state调用 state里的值 类式组件定义 state:

    • 可以在构造函数中初始化 state
    • 可以在类中添加属性 state来初始化

    函数式组件中的使用

    • React16.8 前,函数式组件并不能有自己的 state(因为会重复初始化数据)
    • React16.8 后,出现了 Hook方法,使得函数式组件也可以使用 state特性,先了解即可,后面会教学。
    function Demo(){
        const [weather, setWeather] = React.useState("Winter");
        return <h2>当前季节为:{weather}</h2>     // 当前季节为:Winter
    }
    

    修改 state

    类式组件的函数中,你会发现直接修改 state的值,如:

    this.state.count = 1
    

    如果你在页面中使用了 count这个值,你会发现,这页面咋没变呢?
    梳理一下页面渲染靠的是哪个函数呢?不是靠的 render()函数吗?
    如果你每次直接修改 state后在调用 render()函数的话,不会显得太麻烦了吗?
    其实React也不建议 state不允许直接修改,而是通过特定的渠道来修改,便是使用在类的原型对象上的方法 setState()

    setState
    this.setState(partialState, [callback]);
    
    • partialState: 需要更新的状态的部分对象
    • callback: 更新完状态后的回调函数

    setState 有两种写法: 写法1:

    this.setState({
        count: 1,
    })
    

    写法2:

    // 传入一个函数,返回x需要修改成的对象,参数为当前的 state
    this.setState(state => ({count: state.count+1});
    

    使用那种写法,取决于修改的状态是否需要动用到当前的状态

    forceUpdate

    还有一种修改状态的方法,就是 forceUpdate,意思是强制更新,即强制更新状态。
    参数为更新状态完成后的回调函数

    this.forceUpdate([callback]);
    

    setState更新与forceUpdate更新都是一种合并操作,而不是替换操作


    • 在执行 setState操作后,React会自动帮我们调用一次 render()
    • render() 的执行次数便是 1+n (1 为初始化时的自动调用,n 为状态更新的次数(即调用 setStateforceUpdate的次数))
    • 尽量少用或不用 forceUpdate

    2. props

    state不同,state是组件自身的状态(数据),而 props则是外部传入给自己的状态(数据)

    props在组件内修改,必须由谁传入的即由谁修改

    类式组件中使用

    class Person extends React.component{
    
        constructor(props){     // 还记得构造函数的参数吗,也能够获取 props
            super(props);
        }
    
        // 可以使用静态属性 propTyps 来限制props
        static propTypes = {
             // 在 16版本前,通过使用React自带的PropTypess属性传递值
            // name: React.PropTypes.string.isRequired,
            
            // 16版本后,PropTypes被单独移出 React作为单独的个体,需要另外导入
            name: PropTypes.string.isRequired, // 字符串类型,且必须传值
            sex: PropTypes.string,  // 字符串类型
            age: PropTypes.number,  // 数字类型
            speak: PropTypes.func,  // 函数类型
        }
        
        // 可以使用静态属性 defaultProps 来设置某些 prop 的默认值
        static defaultProps = {
            sex: "男",   // 设置 sex 的 prop 默认值为 “男”
        }
    
        render(){
            const {name, sex, age} = this.props;    // 从 props中获取值
            return (
                <ul>
                    <li>姓名:{name}}</li>
                    <li>性别:{sex}</li>
                    <li>年龄:{age}</li>
                </ul>
            )
        }
    }
    
    // ReactDOM.render( <Person name="Tom" sex="男" age="16"/>, document.querySelector("#root"));    // 以类似属性的方式传递 props值
    
    ReactDOM.render(<Person {...p} />, document.querySelector("#root"));  
    // 可以用扩展运算符来传递 props值
    

    在使用的时候可以通过 this.props来获取值 类式组件的 props:

    1. 通过在组件标签上传递值,在组件中就可以获取到所传递的值
    2. 在构造函数的参数里可以获取到 props
    3. 可以分别设置 propTypesdefaultProps 两个属性来分别操作 props的规范和默认值,两者都是直接添加在类式组件的原型对象上的(所以需要添加 static

    函数式组件中使用

    还记得说函数式组件的参数是什么吗?便是 props
    传入的方式与类式组件相同,都是在组件标签中传递值

    function Person(props){   // 参数为 props
        const {name, sex, age} = props;     // 使用占位符获取 props的值
        return (
            <ul>
                <li>姓名:{name}}</li>
                <li>性别:{sex}</li>
                <li>年龄:{age}</li>
            </ul>
        )
    }
    
    Person.defaultProps = {     // 设置 props默认值
        sex: "男",
        age: 18,
    }
    Person.propTypes = {    // 设置 props限制
        name: PropTypes.string.isRequired,
        sex: PropTypes.string,
        age: PropTypes.number,
    }
    
    const p = {name:"Jerry", sex:"女", age:16}
    ReactDOM.render(<Person {...p}/>,document.querySelector("#app"));
    

    函数组件的 props定义:

    1. 同样也是在组件标签中传递 props的值
    2. 组件函数的参数为 props
    3. props的限制和设置默认值也同样实在原型对象上

    3. refs

    假设在你的组件里有着表单元素,然后你需要获取到其中的值,该怎么获取呢?有的人可能想给元素绑定 id后用 document.querySelector来查找元素,但这样就违背了 React的想法,又开始操作起了DOM元素,所有React提供了第三种特性 refs

    React的历史中,共有三种操作refs的方法,分别为:

    • 字符串形式
    • 回调形式
    • createRef形式

    让我们一一来了解一下:

    类式组件中使用 refs

    (1) 字符串形式的 refs
    class Demo extends React.Component{
        
        showData(){
             // 通过自己设定的 refs名称获取对应的元素
            const {myInput} = this.refs;   // 返回该元素
            alert(myInput.value)
        }
        
        render(){
            return (
                <div>
                    { /* 通过 ref属性绑定这个 input标签 */ }
                    <input type="text" ref="myInput" placeholder="search something" />
                    {/* 事件绑定会在下面讲 */}
                    <button onClick={this.showData}>Show Data</button>
                </div>
            )
        }
    }
    
    (2) 回调形式的 refs

    其实很简单,但在后来的版本中,官方觉得这样做有问题,于是推出了全新的版本:使用回调函数来操作 refs

    class Demo extends React.Component{
        
        showData(){
            const {myInput} = this;   // 返回该元素
            alert(myInput.value)
        }
        render(){
            return (
                <div>
                    {/* 回调函数的参数为该元素本身,通过函数绑定在 this上 */}
                    <input type="text" ref={ e => this.myInput = e } placeholder="search something" />
                    <button onClick={this.showData}>Show Data</button>
                </div>
            )
        }
    }
    
    (3) createRef形式(推荐)

    后来的React发现,使用回调函数的形式还是会出现一些不可言喻的问题,所以,又一次推出了全新的的方法来使用 refs,便是使用 React身上的方法 createRef()

    class Demo extends React.Component{
    
        // 使用 React.createRef() 创建一个 ref容器
        myRef = React.createRef();
    
        showData = ()=>{
            // 在容器中获取 DOM元素
            const {current} = this.myRef;
            alter(current.value);
        }
    
        render(){
            return (
                <div>
                    {/* 将DOM元素绑定在容器中 */}
                    <input ref={this.myRef} placeholder="点击提示数据" type="text"/>
                    <button onClick={this.showData}>ShowData</button>    
                </div>
            )
        }
    }
    

    注意:一个 ref容器,只能存储一个元素(专人专用),后加入的会把前一个顶出去

    函数式组件中使用 refs

    • React16.8前,与 state相同,函数式组件也无法使用 refs特性
    • React16.8后,出现了 Hook函数,使得函数式组件也有了使用 refs特性的能力

    以下代码现阶段无需看懂先,了解即可。

    function Demo() {
        const [count, setCount] = React.useState(0);
        function add(){
            setCount(count=> count+1);
        }
        return (
            <div>
                <h2>当前求和:{count}</h2>
                <button onClick={add}>点击加一</button>
            </div>
        )
    }
    

    四、组件的事件绑定

    1. 在函数式组件中进行事件绑定

    可以在函数中定义另外一个函数(内置函数)然后进行调用即可

    function Demo(props){
        function showData(){
            console.log(props);
            alert("触发了事件");
        }
        return (
            <div>
                <button onClick={showData}>点击触发</button>
            </div>
        )
    }
    

    2. 在类式组件中进行事件绑定

    在类式组件中可以通过类本身的方法来调用

    class Demo extends React.Component{
        state = { count: 0 }    // 定义 state
        showData(){
            alert("触发了事件")
            console.log(this.state.count);; 报错
        }
        render(){
            return (
                <div>
                    <button onClick={this.showData}></button>
                </div>
            )
            // 成功弹出弹窗,但是输出报错
        }
    }
    

    这里你会发现,虽然成功 alter,但是输出 state会报错,这是为什么呢?

    平时在组件内使用 this.state的时候,此时 this的指向都是作用于类的原型对象,即类本身。
    而在函数中,因为使用了 babel.js的缘故,js默认开启了严格模式,所以函数体的 thisundefined,自然找不到在原型对象上的 state
    此时有两种解决方法:

    • 使用 bindapply等方法改变 this指向
    • 使用箭头函数,改变 this指向(推荐)
    class Demo extends React.Component{
        constractor(props){
            super(props);
            // this.showData = this.showData.bind(this);   // 方法1
        }
        
        state = { count: 0 }    // 定义 state
        showData = ()=>{    // 方法2
            alert("触发了事件")
            console.log(this.state.count);; 报错
        }
        render(){
            return (
                <div>
                    <button onClick={this.showData}></button>
                </div>
            )
            // 成功弹出弹窗,但是输出报错
        }
    }
    

    3. React的事件处理方法

    • 通过 onXxx属性指定事件处理函数(注意大小写
      • React使用的是自定义(合成)事件,而不是原生的 DOM事件(为了更好的兼容性)

      • React的事件是通过事件委托方式处理的(委托给组件最外层的元素)(为了更加的高效)

      • 可以通过事件的 event.target获取发生的DOM元素对象,可以尽量减少 refs的使用

      • 在绑定事件的时候不要加括号,这会被 jsx识别为执行函数

      • 在类式组件中绑定函数注意 this的指向问题(推荐绑定的函数都使用箭头函数

    五、组件的生命周期

    Vue熟悉的应该对生命周期并不陌生。
    组件在挂载到页面上的过程中,期间发生了许多的事情,而生命周期,可以看作是一个组件的执行流程,我们可以通过在某个流程节点中进行一些操作,而这就是 生命周期钩子(函数)
    注:生命周期函数只适用于类式组件,并不适用于函数式组件,但是在React16.8之后的 Hook,也可以让函数式组件实现类似于生命周期钩子的功能

    1. React 16之前

    React快速暴力入门

    以下是流程:

    • 自身组件挂载
      1. constructor: 构造函数(初始化)
      2. componentWillMount: 组件挂载前
      3. render: 组件挂载中
      4. componentDidMount: 组件挂载完成后
    • 拥有父组件且父组件状态进行更新(setState):
      1. 父组件 shouldComponentUpdate: 父组件是否进行状态更新
      2. 父组件 componentWillUpdate: 父组件状态更新前
      3. 父组件 render: 父组件更新挂载中
      4. 子组件 componentWillReceiveProps: 子组件将收到新的Props
      5. 子组件 shouldComponentUpdate: 子组件是否进行状态更新
      6. 子组件 componentWillUpdate: 子组件状态更新前
      7. 子组件 render: 子组件更新挂载中
      8. 子组件 componentDidMount: 子组件挂载完成
      9. 父组件 componentDidMount: 父组件挂载完成
    • 当组件要进行卸载时:
      1. componentWillUnmount: 组件卸载前

    有几个需要注意的地方:

    • shouldComponentUpdate的意思是是否更新状态,所以他应该有个布尔类型返回值,而且默认为 true(不写的时候),当你写了这个钩子时别忘了写个返回值,这个横眉周期一般用于进行更新值的一些判断。
    • shouldComponentUpdate有两个参数,分别是 nextProps:新的propsnextState: 新的state,此时他们都还没更新到组件上。
    • 使用 setState时会经过生命周期 shouldComponentUpdate,但是使用 forceUpdate时则不会,而是直接到 componentWillUpdate生命周期

    2. React 16之后

    React快速暴力入门

    新旧生命周期对比:

    1. 在新的生命周期中,舍弃了(即将舍弃)三个生命周期函数:
      • componentWillMount
      • componentWillReceiveProps
      • componentWillUpdate
    2. 新增了两个生命周期函数:
      • getDerivedStateFromProps

      • getSnapshotBeforeUpdate

    以下是流程:

    • 自身组件挂载:
      1. constructor: 构造函数(初始化)
      2. getDerivedStateFromProps: 从props中获取派生的state
      3. render: 组件挂载中
      4. componentDidMount: 组件完成挂载
    • 父组件更新时
      1. 父组件 getDerivedStateFromProps: 从 props中获取派生的state
      2. 父组件 shouldComponentUpdate: 判断是否进行状态更新
      3. 父组件 render: 父组件挂载中
      4. 子组件 getDerivedStateFromProps: 从 props中获取派生的state
      5. 子组件 shouldComponentUpdate: 判断是否进行状态更新
      6. 子组件 render: 子组件挂载中
      7. 子组件 getSnapshotBeforeUpdate: 子组件获取状态更新前的快照
      8. 子组件 componentDidUpdate: 子组件完成更新
      9. 父组件 getSnapshotBeforeUpdate: 父组件获取状态更新前的快照
      10. 父组件 componentDidUpdate: 父组件完成更新
    • 组件卸载时
      1. componentWillUnmount: 组件卸载前

    也有几个需要注意的地方:

    • getDerivedStateFromProps 需要定义个实例本身,所以是静态方法
    • getDerivedStateFromProps 有两个参数,分别是当前的 propsstate
    • 若需要组件的状态任何时候都取决于 props则可以使用 getDerivedStateFromProps,但是使用场景比较罕见,可以在其中定义 state
    • getSnapshotBeforeUpdate 有两个参数,分别为状态更新前的 propsstate,并且有一个返回值(快照),一般用作本次更新情况的说明情况。
    • componentDidUpdate 有三个参数,分别为状态更新前的 propsstate,以及先前 getSnapshotBeforeUpdate返回的快照。
    • 其他的与旧的生命周期没有什么差别

    六、组件列表渲染、条件渲染与DOM的Diffing算法

    1. 组件列表渲染

    在有时候我们需要批量的去创建一些DOM元素或组件,比如页面上的:新闻列表、推文列表、好友列表等等,你会发现在开发的过程中经常会使用到列表,但是自己一个个的去写DOM回十分繁琐。
    我们可以通过数组存储数据,也可以使用数组来循环渲染数据。
    举个例子:

    class Demo extends React.Component{
        state = {
            arr: [a, b, c],
        }
        render(){
            // 输出 abc
            return (
                <div>
                    <ul> {stus} </ul>
                </div>
            )
        }
    }
    

    你会发现,React输出数组会把所有的元素直接循环输出出来,那么我们只要在每个元素左右添加上标签,那不就构成了列表渲染吗?
    这里我们可以通过 jsx语法配合ES6语句来实现列表渲染。
    直接上代码:

    class Demo extends React.Component{
        state = {
            stus: [
                {id: "001", name: "小明", age: "28"},
                {id: "002", name: "小红", age: "26"},
            ],
        }
        render(){
            return (
                <div>
                    <ul>
                        {
                            this.state.stus.map(item =>{
                                return <li key={item.id}>{item.name}---{item.age}</li>
                            })
                        }
                    </ul>
                </div>
            )
        }
    }
    

    React快速暴力入门

    2. key 的使用

    有的人发现了写列表渲染的时候,我都会给每个标签加上一个 key属性,这是为什么呢?
    其实 key的作用是给当前的标签添加一个唯一的标识,用于给React进行 diffing算法计算的时候使用

    3. diffing 算法

    当状态发生改变时,react会根据【新的状态】生成【新的虚拟DOM】
    然后将新旧虚拟DOM进行 diff比较,比较规则如下:

    1. 旧虚拟DOM 中找到了与 新虚拟DOM 相同的 key
      • 若虚拟DOM中的内容没变,则直接使用之前的真实DOM
      • 若虚拟DOM中的内容变了,则生成新的真实DOM并进行替换           
    2. 旧虚拟DOM 中未找到与 新虚拟DOM 相同的 key,则根据数据创建新的真实DOM,然后渲染到页面

    用index作为key可能引发的问题

    • 对数据进行 逆序添加、逆序删除 邓破坏顺序的操作 会产生没有必要的真实DOM更新,影响效率
    • 对结构中包含输入类的DOM 会产生错误的 DOM更新,同时界面渲染有问题
    • 若仅用于展示数据,那用 index作为 key则没有问题 

    七、React脚手架

    恭喜,终于熬到了脚手架这里了,终于可以一键生成所有东西而不是自己一个个引用了。
    什么是脚手架呢? 脚手架可以说是建房子时的构架,其他人已经帮你吧房子的架构搭好了,不需要自己动手。
    React就有自己的脚手架,开发团队通过 webpackbabelnpm等方法,帮你搭建好了一个使用React开发软件的环境,不再需要自己去创建文件夹,导入React等重复的操作,更加利于我们编写 SPA应用(单页面富应用)。

    1. 安装React脚手架

    在自己的命令行窗口中输入(需要现有 node环境):

    npm i create-react-app -g
    

    全局安装 create-react-app脚手架

    2. 创建React应用

    create-create-app 应用名称
    
    • 应用名称不应该出现大写字母和特殊字符
    • 使用英文命名而不是中文命名

    3. 文件解析

    项目文件中比较常用的文件就以下这些:

    • node_modules ------ npm包的存放位置
    • public ------ 用于存放静态文件
    • src ------ 项目的代码存放位置
      • components ------ 用于存放公用组件的文件夹
      • page ------ 用于存放页面的文件夹
      • App.js ------ 根组件
      • App.css ------ 根组件的样式
      • index.js ------ 项目入口文件
      • index.css ------ 项目的公用样式
    • .gitgnore ------ 编写git的配置文件
    • package.json ------ 项目配置文件
    • README.md ------ 项目信息

    还记得我们的讲组件化的图片吗

    React快速暴力入门

    其中的 APP就是根组件,通过我们编写其他的组件如 HeaderAside等组件,都加装在根组件上。

    4. npm 指令

    npm start       // 使用 webpack-dev-server 启动服务查看应用
    npm build       // 打包生成生产文件
    npm test        // 进行软件测试(不常用)
    npm eject       // 将所有的配置文件暴漏出来(不常用且不建议用)
    

    八、react-router的使用

    1. 什么是 react-router

    react-router 是为了React编写SPA应用操作前端路由而诞生的。

    (1) 前端路由(可能有点直白)

    前端路由是通过HTML5的新API History来操作的,其原理就是url地址的地址发生改变,但是并不会触发重新加载,同时javascript可以监听到改变。
    有两种类型:

    • HashRouter: 利用url地址栏# 后面的哈希值

    React快速暴力入门

    • BrowserRouter: 利用浏览器的History API,地址栏中不包含 # ,显得更加美观

    React快速暴力入门

    (2) SPA 应用

    SPA 全称 Single Page web Application,顾名思义是只有一个页面的应用,通过javascript进行实时的渲染来更新页面。
    即通过当前的路由来判断页面应该加载什么组件,从而呈现不同的页面与效果。

    2. 使用 react-router

    (1) 安装与使用

    在项目文件夹中打开命令行窗口进行 npm下载

    npm i react-router-dom -S
    

    注意:我们下载的是 react-router-dom 而不是 react-router 两者区别:

    • react-router:提供了router的核心 API。如RouterRouteSwitch等,但没有提供有关dom操作进行路由跳转的API
    • react-router-dom:提供了BrowserRouterRouteLink等api,可以通过dom操作触发事件控制路由

    (2) 常用组件

    a. 路由跳转

    在多页面应用中,通常都是使用 a标签进行页面跳转

    <a href="http://localhost:3000">跳转页面</a>
    

    使用单页面富应用中使用react-router则使用路由跳转组件

    import {Link, NavLink} from "react-router";
    
    <Link activeClassName="nav-active" className="nav" to="/about">About</Link>
    <NavLink activeClassName="nav-active" className="nav" to="/home">Home</NavLink>
    
    • activeClassName: 处于当前路由时,对应的组件会自动添加该类
    • className: 当前组件类名
    • to: 当前组件所对应的路由

    Link组件与 NavLink组件都可以进行路由的跳转,区别在于:当前路由对应的NavLink会自动添加class: active,而 Link不会。

    b. 注册路由
    import {Route} from "react-router";
    
    <Route path="/home" component={Home}></Route>
    <Route exact path="/about" component={About}></Route>
    
    • path: 所要监听的路由
    • component: 该路由要绑定的组件
    • exact: 可选,不写时为 false,是否选择严格匹配

    当当前路由对应上了路由组件所绑定的路由时,则会展示所绑定的组件。

    (a) 路由严格匹配与模糊匹配

    路由不仅仅只有一级,有的时候是有多级嵌套的,比如以下这张:

    React快速暴力入门
    模糊匹配严格匹配,都是指当前的组件对当前路由的匹配模式:

    • 模糊匹配: 如果当前路由与匹配的路由成相等或包含(注意层级)的情况,则启用该组件
      • http://localhost:3000/home/a/b/c 则为包含路由 /home
      • http://localhost:3000/a/b/home/c 则为不包含路由 /home层级不对
    • 严格匹配: 如果当前路由与匹配的路由相等的话,才启用该组件
      • http://localhost:3000/home 则为与路由 /home 相等
      • http://localhost:3000/home/a 则为与路由 /home 不相等
    c. 重定向路由

    你明明设置好了路由 /home,但是有的用户就喜欢对着干,在地址栏输入了 /nothing,而你没有注册这个路由,那该怎么办呢?
    这个时候你就可以东涌道重定向路由了,对于没有注册过的路由,都会被跳转到你指定的某个路由去,这就是重定向路由
    经常可以用作一些404页面丢失等情况的路由跳转方式。

    import {Redirect, Route} from "react-router";
    
    <Route ....../>
    <Route ....../>
    <Redirect to="/home"/>
    
    • to: 需要重定向到哪个路由?

    Redirect需放在所有Route下面,当上面的 Route都没有匹配到时,则路由将重定向到指定的路由。

    d. Switch 路由

    你想想,如果你的路由中,出现了 /home/home/abc/home/a/b/c 等这样的路由,当路由为 /home时则会三个路由都同时渲染,但是你又只想要渲染其中的一条,这个时候我们就可以使用 Switch组件。
    使用 Switch组件包裹住所有的 RouteRedirect,当出现多个匹配的路由时,只会渲染第一个匹配的组件。

    import {Switch, Route, Redirect} from "react-router";
    
    <Switch>
        <Route ..../>
        <Route ..../>
        <Redirect to="..."/>
    </Switch>
    
    e. 路由器

    你想要使用路由跳转组件和路由组件,还差一个路由器组件,同时路由器组件必须包裹着这两个组件。

    import {HashRouter, BrowserRouter} from "react-router";
    

    一般为了使整个React应用都可以使用到路由组件,所以一般我们都是把路由器包裹在根组件上的。

    ReactDOM.render( 
        <BrowserRouter>
            <App/>
        </BrowserRouter>,
        document.querySelector("#root")
    );
    

    有两种路由器组件,分别是HashRouterBrowserRouter,分别对应者两种路由方式。

    (3) 路由组件

    路由组件与一般组件

    1. 写法不同
      • 一般组件:<Demo></Demo>
      • 路由组件:<Route path="/demo" component={Demo}/>
    2. 存放位置不同
      • 一般组件:components文件夹
      • 路由组件:page 文件夹
    3. 接受到的 props 不同
      • 一般组件:根据组件标签传递了上面,就收到了什么
      • 路由标签:会收到三个固定的属性
    {
      "history": {
        "length": 18,
        "action": "PUSH",
        "location": {
          "pathname": "/home",
          "search": "",
          "hash": "",
          "key": "tvfyve"
        }
      },
      "location": {
        "pathname": "/home",
        "search": "",
        "hash": "",
        "key": "tvfyve"
      },
      "match": {
        "path": "/home",
        "url": "/home",
        "isExact": true,
        "params": {}
      }
    } 
    

    3.嵌套路由

    假设我们有个路由组件为 Home,在根组件中使用 Link跳转到了该路由,当前路由为 /home,可在组件 Home 中还有两个 Link,分别导向路由 /home/message/home/news 然后在组件中还有其他的路由组件。这就时嵌套路由的使用。
    如下面的代码:

    class Home extends Component{
        render(){
            return (
                <div>
                    <Link to="/home/message">Message</Link>
                    <Link to="/home/news">News</Link>
                    <hr/>
                    <Route path="/home/message" component={Message} />
                    <Route path ="/home/news" component={News} />
                </div>
            )
        }
    }
    

    4. 编程式路由

    如果说,我们想要做用户点击按钮登陆后,如果他是老师就去老师页面,如果是学生就去学生页面,这个显然单靠 Ljnk 无法完成,我们可以通过 js进行路由的跳转(也是 react-router 基于 History API 编写的)

    class Message extends Component {
        state = {
            messageArr:[
                {id:"01", title: "消息1"},
                {id:"02", title: "消息2"},
                {id:"03", title: "消息3"},
            ]
        }
        // 编程式路由导航
        pushShow = (id, title)=>{
            // push跳转 + 携带 params参数
            this.props.history.push(`/home/message/detail/${id}/${title}`);
            
            // push跳转 + 携带 search参数
            // this.props.history.push(`/home/message/detail?id=${id}&title=${title}`);
            
            // push跳转 + 携带 state参数
            // this.props.history.push(`/home/message/detail`, {id,title});
        }
        replaceShow = (id, title)=>{
            // replace跳转 + 携带 params参数
            this.props.history.replace(`/home/message/detail/${id}/${title}`);
            
            // replace跳转 + 携带 search参数
            // this.props.history.replace(`/home/message/detail?id=${id}&title=${title}`);
            
            // replace跳转 + 携带 state参数
            // this.props.history.replace(`/home/message/detail`, {id, title});
        }
        // 后退
        goBack = ()=>{
            this.props.history.goBack();
        }
        // 前进
        goForward = ()=>{
            this.props.history.goForward();
        }
        // 跳转指定位置
        go = ()=>{
            // 向前两步
            this.props.history.go(2);
            
            // 后退两步
            this.props.history.go(-2);
        }
        render() {
            const {messageArr} = this.state;
            return (
                <div>
                    <ul>
                        {
                            messageArr.map(item=>{
                                return (
                                <li key={item.id}>
                                    <Link to={`/home/message/detail/${item.id}/${item.title}`}>{item.title}</Link>
                                    &nbsp;<button onClick={() => this.pushShow(item.id, item.title)}>push查看</button>
                                    &nbsp;<button onClick={() => this.replaceShow(item.id, item.title)}>replace查看</button>
                                </li>
                                )
                            })
                        }
                    </ul>
                    <hr/>
                    <Route path="/home/message/detail/:id/:title" component={Detail} />
                    <button onClick={this.goBack}>goBack</button>
                    &nbsp;
                    <button onClick={this.goForward}>goForward</button>
                    &nbsp;
                    <button onClick={this.go}>go</button>
                </div>
            )
        }
    }
    

    总结一下上面的代码:

    1. 编程式路由都是通过 props中的 history对象进行操作(都是该对象身上的方法,调用方式为:this.props.history.xxx
    2. 常用方法:
      • push(route[, state]): 跳转到指定路由(带有历史记录)

      • replace(route[, state]): 跳转到指定路由(不带有历史记录)

      • goBack(): 后退一个

      • goForward(): 前进一个

      • go(num): 前往指定步数,当 num为正数时,为前进,当 num为负数时则为后退。

    5. withRouter 组件

    有的时候,我们想要在其他组件中也使用路由组件的功能,比如导航栏,应该属于公用组件,但是里面的导航链接的功能却是路由组件的功能,我们应该怎么解决呢?
    react-router 中,提供了这么一种方法,可以让一般组件具有路由组件的功能,则就是 withRouter() 方法。
    看看演示:

    import {withRouter} from "react-router-dom";
    
    class Header extends Component {
        // withRouter后该组件也有了路由组件的功能
        goBack = ()=>{
            this.props.history.goBack();
        }
        go = ()=>{
            this.props.history.go(2);
        }
        goForward = ()=>{
            this.props.history.goForward();
        }
        render() {
            return (
                <div>
                    <h1>This is a React-router-dom Test!</h1>
                    <button onClick={this.goBack}>goBack</button>
                    &nbsp;
                    <button onClick={this.goForward}>goForward</button>
                    &nbsp;
                    <button onClick={this.go}>go</button>
                </div>
            )
        }
    }
    
    // withRouter 用于给一般组件添加上路由组件特有的功能,返回一个新组件
    export default withRouter(Header);
    

    九、路由组件之间的参数传递

    父子组件之间的内容传递,可以通过 props来进行参数传递,但是路由组件却没有自己的标签,那么该如何进行参数的传递呢?
    还记得路由组件特有的 props吗,我们可以利用 History API的一些特性来进行路由组件之间的参数传递,有三种方法。

    • params
    • search
    • state

    1. 传递 params 参数

    // 父组件
    class Parent extends Component {
        state = {
            messageArr:[
                {id:"01", title: "消息1"},
                {id:"02", title: "消息2"},
                {id:"03", title: "消息3"},
            ]
        }
        render() {
            const {messageArr} = this.state;
            return (
                <div>
                    <ul>{
                     messageArr.map(item=>{
                        // 向路由组件传递 params参数
                        return <li key={item.id}><Link to={`/home/message/detail/${item.id}/${item.title}`}>{item.title}</Link></li>
                        })
                    }</ul>
                    <hr/>
                    {/* 声明接受 params参数,可以在 props中的 match属性的 params属性里获得 */}
                    <Route path="/home/message/detail/:id/:title" component={Child} />
                </div>
            )
        }
    }
    
    // 子组件
    class Child extends Component {
        state = {
            contentArr:[
                {id:"01", content: "你好中国"},
                {id:"02", content: "你好世界"},
                {id:"03", content: "你好帅哥"},
            ]
        }
        render() {
            console.log(this.props);
            // 获取 params参数
            const {id, title} = this.props.match.params
            const findResult = this.state.contentArr.find(obj=>obj.id == id).content;
            return (
                <div>
                    <ul>
                        <li>ID: {id}</li>
                        <li>TITLE: {title}</li>
                        <li>CONTENT: {findResult}</li>
                    </ul>
                </div>
            )
        }
    }
    

    在使用 params传递参数时,你会清楚的看到参数以路由的形式展现了出来,例如:

    • http://localhost:3000/home/message/用户1/文章32

    类式上面的用户1文章32,这就是传递的参数。
    params方式传递的参数,在路由中需要声明接受才可以使用。

    <Route path="/home/message/detail/:id/:title" component={Child} />
    

    后面的 :id:title 就是可变路由,可以通过propsmatch接受接收这里的内容.

    const {id, title} = this.props.match.params
    

    2. 传递 search 参数

    这个就是依赖 get的请求方式。

    • http://localhost/home/message?id=1&title=abc

    javascript 可以获取到 url? 后面的请求体。 所以我们可以吧上面的 map 中返回的标签修改以下

    <li key={item.id}><Link to={`/home/message/detail?id=${item.id}&title=${item.title}`}>{item.title}</Link></li>
    

    且这种方式不需要在路由中声明接收。
    通过 props 中的 location 进行接收

    const search = this.props.location.search;
    // 获取到的格式时: ?id=xxx&title=xxx 所以还需要加工一下
    const {id, title} = qs.parse(search.slice(1));
    

    3. 传递 state 参数

    通过 HistoryAPI来进行数据传输:

    <li key={item.id}><Link to={{pathname: `/home/message/detail`, state:{id:item.id, title:item.title}}}>{item.title}</Link></li>
    

    不需要在路由链接中添加任何东西,也不需要路由进行声明接收。更加的美观。
    props中的 location 里获取 state属性

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

    4. 三者对比

    路由组件传参其实用的较少,因为他们都有对某些东西的依赖:

    • params: 依赖于路由链接,链接不美观
    • search: 依赖于路由链接,链接不美观
    • state: 依赖于历史记录 (HistoryAPI),链接美观,但是当直接输入链接时则会报错(没有历史记录)

    所以尽量使用其他的传参方式,如果非要使用的话,有限度比较为:

    1. params (最为常用)
    2. search (较为常用)
    3. state (比较少用)

    十、redux 的使用

    React快速暴力入门

    • 方法1:通过不断的 props进行传参,但是这非常的费时费力
    • 方法2:使用 Pubsub.js等进行消息发布/订阅功能
    • 方法3:使用 react-redux进行数据集中式管理

    redux可以看作一个管家,负责帮忙存储公共的数据。

    1. 安装 redux

    npm i redux -S
    

    2. 核心概念

    React快速暴力入门

    redux 有着三个核心概念

    1. action:
      • 动作的对象(操作内容)
      • 包含两个属性: a. type: 表示属性,值为字符串,唯一,必要属性(要干嘛) b. data: 数据属性,值为任意类型,可选属性(怎么干)
      • 例如:{ type: "ADD_STUDENT", data: { name: "tom", age: 18 } }
    2. reducer:
      • 用于初始化状态和加工状态(对数据进行初始化和操作数据的)
      • 加工时,根据旧的 stateaction,产生新的 state的纯函数
      • 有两个参数,一个为之前的状态(prevstate)与动作对象(action
    3. store:
      • stateactionreducer联系在一起的对象(大脑)

    3. 基本使用

    (1) 创建文件夹

    src文件夹中创建 redux文件夹,用于存放 redux的相关内容

    • Compnoent ------ 存放组件相关的文件夹
    • redux ------ 存放 redux相关内容的文件夹
      • actions ------ 存放 action相关内容的文件夹
      • reducers ------ 存放 reducer相关内容的文件夹
      • constant.js ------ 存放规范命名的文件
      • store.js ------ 编写 store的文件

    由我来一个个带你们解析。先写一个最简单的 redux

    /**
     * store.js
     * 该文件专门用于暴漏一个 store对象,整个应用只有一个 store对象
     */
    // 引入 createStore,专门用于创建 redux中最为核心的 store
    import {createStore} from "redux";
    // 引入为 Count组件服务的 reducer
    import countReducer from "./count_reducer";
    const store = createStore(countReducer)
    // 暴露 store对象
    export default store;
    
    /**
     * / reducer / count.js
     * 1. 该文件是用于创建一个为 Count组件服务的 reducer,reducer的本质就是一个函数
     * 
     * 2. reducer函数会收到两个参数,分别为:之前的状态(preState),动作对象(action)
     * 
     * 3. 会自动调用一次 reducer(初始化)
     */
    // 初始化的状态
    const initState = 0;
    export default function countReducer(preState = initState, action){
        if(preState === undefined) preState = 0; 
        // 从 action对象中获取 type,data
        const {type, data} = action;
        // 根据 type觉得如何加工数据
        switch (type) {
            case 'increment': // data
                return preState + data;
            case 'decrement': // 如果是减
                return preState - data;
            default:
                return preState;
        }
    }
    
    // / Component / Count.js 
    import React, { Component } from 'react'
    // 引入store
    import store from "../../redux/store";
    export default class Count extends Component {
        // 加法
        increment = ()=>{
            const {value} = this.selectNumber;
            // 发送动作对象给 store
            store.dispatch({
                type: "increment",
                data: value*1
            })
        }
        render() {
            return (
                <div>
                    <h1>当前求和为:{store.getState()}</h1>
                    <select ref={c=>this.selectNumber = c}>
                        <option value="1">1</option>
                        <option value="2">2</option>
                        <option value="3">3</option>
                    </select>
                    <button onClick={this.increment}>+</button>
                </div>
            )
        }
    }
    

    以上就是最精简的 redux,让我们分析一下流程:

    1. 编写 Count组件,创建 Count组件对应的 Reducer文件
    2. 编写 Reducer的代码并抛出::
      • Reducer是个纯函数
      • 通常使用 Switch进行 action中的 type的判断
      • 函数返回值为修改后的值
    3. 创建 store并编写代码:
      • 使用方法 createStore()方法来创建一个 store,参数是一个 reducer
      • 把编写好的 Count组件的 reducer导入
    4. 在组件中引入 store并进行调用:
      • 在方法中通过使用 dspatch()方法向 store传递 action
      • dispatch的参数时一个对象(即 action动作对象)

    (2) 使用流程

    以上使创建的大致流程,而使用的大致流程是这样的:

    (3) 使用异步redux

    如果我们需要使用异步的 redux的话,还需要借助另一款插件redux-thunk

    npm i redux-thunk -S
    

    这是一个中间件,用于帮忙处理异步的 redux
    异步的 action的值为一个函数 在函数中进行普通的 dispatch() 操作

    export const createIncrementAsyncAction = (data, time=500)=>{
        // 返回一个 action
        return ()=>{
            setTimeout(()=>{
                store.dispatch(createIncrementAction(data));
            }, time);
        }
    }
    

    同时我们要在 store处设置让他支持执行中间件,通过 reduxapplyMiddleware() 方法就可以加载中间件,他的参数就是中间件,然后 applyMiddleware() 将作为 createStore() 的第二个参数引入。

    // store.js
    // 引入 applyMiddleware,专门用于执行中间件
    import {createStore, applyMiddleware} from "redux";
    // 引入为 Count组件服务的 reducer
    import countReducer from "./count_reducer";
    // 引入 redux-thunk,用于支持异步 action
    import thunk from "redux-thunk";
    // 暴露 store对象
    export default createStore(countReducer, applyMiddleware(thunk));
    

    (4) 监听状态变化

    你写着写着有没有发现,虽然 redux里面的状态确实更新了,但是页面并没有变化啊?
    还记得页面渲染使用的是哪个函数吗?render()函数。可是在 redux状态发生变化时,并不会帮助我们调用 render()函数,所以我们需要手动实现实时渲染页面。
    在这里我们使用到了 reduxstore上的 subscribe() 方法,用于监听 redux上状态的变化,参数是一个函数,便于我们进行操作。
    一般我们都写在根标签上(精简些,不用再每个使用 redux的组件中都写一遍)

    import React from 'react';
    import ReactDOM from 'react-dom';
    import './index.css';
    import App from './App';
    import store from "./redux/store"
    ReactDOM.render(
        <App />,
      document.getElementById('root')
    );
    // 精简写法
    store.subscribe(()=>{
      ReactDOM.render(
          <App />,
        document.getElementById('root')
      );
    })
    

    4. 注意事项

    1. 一般使用到 redux的话,需要使用的组件肯定不止一个,所以创建 actionsreducers用来存储多个 actionreducer
    2. 同上,store肯定不止加载一个 reducer,所以我们使用 reduxcombineReducers()方法来整合所有的 reducer
      • combineReducers()方法的参数是一个对象,里面存放着所有的 reducer
    import {createStore, applyMiddleware, combineReducers} from "redux";
    import countReducer from "./reducers/count";
    import personReducer from "./reducers/person";
    import thunk from "redux-thunk";
    // 汇总所有的 reducer
    const allReducer = combineReducers({
        count: countReducer,
        persons: personReducer,
    });
    export default createStore(allReducer, applyMiddleware(thunk));
    
    
    
    1. 因为 redux在设计上还有许多的问题,例如:
      • 单个组件需要做:与 store打交道,获取数据,监听数据变化,派发 action对象等,一个组件负责的事情太多了。
      • 需要另外监听 redux的状态变化来更新状态并渲染页面。
      • 所以有人对 redux进行了优化,推出了另一个库 react-redux(放心,没啥不同,就是多了点优化,后面会讲)
    2. 能不使用 redux,就不要使用 redux(不管是 redux 还是 react-redux
    3. 能不使用 redux,就不要使用 redux(不管是 redux 还是 react-redux
    4. 能不使用 redux,就不要使用 redux(不管是 redux 还是 react-redux

    十一、了解与使用 react-redux

    前面也说了,react-redux 其实就是 redux的升级版,对许多地方进行了优化,但在学习他之前,需要我们进行一些对 redux的优化知识。

    React快速暴力入门

    1. 使用容器组件和UI组件

    其目的就是为了把组件身上太多的活进行拆分,分为UI组件(内组件)容器组件(外组件),两个组件之间使用 props进行通信,对 store那边的请求状态,更改状态的活交给容器组件来干,而通过状态来编写页面,更新渲染等活,就交给 UI组件来干。

    了解了这个后,就可以开始使用 react-redux

    2. 安装 react-redux

    npm i react-redux -S
    

    这个就不多说了。

    3. 创建文件夹

    对于容器组件,我们都是使用 containers文件夹进行存储。

    • containers ------ 用于存储容器组件的文件夹
    • redux ------ 用于存储 react-redux相关的文件夹

    4. 创建容器组件

    容器组件通过 react-reduxconnect() 方法进行创建。

    // 引入 Count的 UI组件
    import Count from "../../components/Count";
    // 引入 connect用于连接 UI组件与 redux
    import { connect } from 'react-redux'
    
    // 该函数的返回值作为状态传递给 UI组件
    function mapStateToProps(state){
        return {
            count: state,
        }
    }
    // 该函数的返回值作为操作状态的方法传递给 UI组件
    function mapDispatchToProps(dispatch){
        return {
            add: data=>{
                console.log(1234);
            }
        }
    }
    
    export default connect(mapStateToProps, mapDispatchToProps)(Count);
    

    使用 connect( )( )创建并暴露一个 Count组件的容器组件。
    调用 connect时有两个参数,且必须时函数。

    • mapStateToProps( state ): 该函数的返回值会作为 状态 传递给 UI组件
      • state: 参数 statereact-redux 默认操作好的 store.getState()
    • mapDispatchToProps( dispatch ): 该函数的返回值会作为 操作状态的方法 传递给 UI组件,有语法糖写法,传入个对象即可(看下面代码)。
      • dispatch: 参数 dispatchreact-redux 默认给的 store.dispatch 方法

    这里只是为了讲解方便,使用时可以使用语法糖的。

    注意的几个地方:

    1. 一般都是把 UI组件容器组件 写在一个文件中,至于存在哪个文件夹中看公司需求(一般都是 containers
    2. 容器组件里的 store不是通过引入使用,而是作为 props传递给容器组件的标签的。<Count store={store} />
    3. 语法糖写法:
    // 引入 Count的 UI组件
    import Count from "../../components/Count";
    // 引入 connect用于连接 UI组件与 redux
    import { connect } from 'react-redux'
    
    // 精简写法
    export default connect(
        state => ({count: state}),
        {
            add: data=> console.log(1234, data)
        }
    )(Count);
    

    5. Provider 组件

    如果你有许多个容器组件,那么每个容器组件都要传入 store,那么是不是觉得太繁琐了呢?所以 react-redux 提供了 Provider组件用于处理这个问题,只需要在根标签处使用并把 store传递进去,他就可以自动判断哪些组件需要使用 store并自动传递给它。

    import React from 'react';
    import ReactDOM from 'react-dom';
    import './index.css';
    import App from './App';
    import store from "./redux/store"
    // 优化3: 使用自带的 Provider自动判断哪些组件需要使用 store,从而自动导入
    import { Provider } from "react-redux";
    ReactDOM.render(
      <Provider store={store}>
        <App />
      </Provider>,
      document.getElementById('root')
    );
    

    6. 监听状态变化

    有没有发现,我上面的代码已经没有再写 store.subscribe() 监听状态变化了,是因为我们创建容器组件的 connect已经帮我们进行监听了。

    7. 容器组件之间通信

    其实这个很简单,还记的 connect的第一个函数的第一个参数吗?传递给 UI组件的是 props,他的参数是 state,这个 statestore.getState()
    可是此时你的 store.getState() 不再是一个单纯的值,而是所有 reducer的对象,所以我们可以在里面获取到其他容器组件的值。

    export default connect(
        state => ({count: state.count, personLength: state.persons.length}),
        {
            increment: createIncrementAction,
            decrement: createDecrementAction,
            asyncIncrement: createIncrementAsyncAction,
        }
    )(Count);
    

    8. 文件规范

    创建了那么多东西,有的可以整合一下的,让我们来修整修整:

    • containers ------ 用于存放容器组件的文件夹(UI组件与容器组件写在一起)
      • Count.js ------ Count 的容器组件
      • Person.js ------ Person 的容器组件
    • redux ------ 用于存放 react-redux相关的文件夹
      • actions ------ 用于存放所有 action的文件夹

        • count.js ------ 用于存储 Count组件的 action
        • person.js ------ 用于存储 Person组件的 action
      • reducers ------ 用于存放所有 reducer的文件夹

        • count.js ------ 用于存储 Count组件的 reducer
        • person.js ------ 用于存储 Person组件的 reducer
        • index.js ------ 用于存储汇总的 reducer的文件(combineReducers()方法)
      • constant.js ------ 用于存储一些公用的命名的文件

      • store.js ------ react-reduxstore文件

    9. 再说一遍,能不用就别用这东西!!

    十二、React16.8 与一些扩展

    1. lazy() 与 Suspense

    之前的组件,都是一并加载的,这样会给服务器带来较大的负担,所以 react推出了 lazy()懒加载,进行组件的按需加载。
    与之一起出来的是 Suspense,他解决的是组件在加载过程中还没加载出来时的白屏,用于展示其他的内容。

    import React, { Component, lazy, Suspense } from 'react'
    import {Route, Link} from "react-router-dom"
    
    // import Home from "./Home";
    // import About from "./About";
    import Loading from "./Loading";
    // lazy() 方法的参数是一个函数,返回需要加载的组件
    const Home = lazy(()=>import("./Home"))
    const About = lazy(()=>import("./About"))
    export default class index extends Component {
        render() {
            return (
                <div>
                    <Link to="/home">Home</Link>
                    <Link to="/about">About</Link>
                    <hr/>
                    {/* Suspense 用于解决加载组件时的白屏,可以显示其他的内容,而其他内容不允许使用 lazy加载 */}
                    <Suspense fallback={<Loading/>}>
                        <Route path="/home" component={Home}></Route>
                        <Route path="/about" component={About}></Route>
                    </Suspense>
                </div>
            )
        }
    }
    
    • lazy 的参数是一个函数,使用 import 导入一个组件并返回
    • Suspense 的属性 fallback属性的属性值是 组件标签 而不是组件
    • Suspense所使用的组件不能使用 lazy进行懒加载。

    2. Hook

    React16.8可以说是给函数式组件一次春天,因为他有了Hook,可以实现一些 state、生命周期函数,refs等特性。
    让我们一个个来看:

    (1) stateHook

    可以让函数式组件实现使用 state的特性:

    
    export default function Demo() {
        // useState返回一个数组,只有两个元素(只有两个元素)
        // 元素1 为状态,元素2 为更新状态的方法
        // 第一次调用时以及将count进行底层存储,所以 Demo重复渲染不会重置count数据
        const [count, setCount] = React.useState(0); // 初始值赋为 0
        const [name, setName] = React.useState("Tom");
        function add(){
            // 进行状态赋值
            // setCount(count + 1); // 写法1,直接将原来的状态值覆盖
            setCount(count=> count+1); // 写法2,参数为函数,接受原本的状态值,返回新的状态值,覆盖原来的状态
        }
        return (
            <div>
                <h3>名字:{name}</h3>
                <h2>当前求和:{count}</h2>
                <button onClick={add}>点击加一</button>    
            </div>
        )
    }
    

    (2) EffectHook

    可以让函数式组件实现类似生命周期钩子的特性:

    export default function Demo() {
        const [count, setCount] = React.useState(0);
        const [name, setName] = React.useState("Tom");
        function add(){
            setCount(count=> count+1);
        }
        function updName(){
            setName(name=>"Jerry");
        }
        function unmount(){
            ReactDOM.unmountComponentAtNode(document.querySelector("#root"));
        }
        // useEffect接收两个参数,第一个为函数体,第二个为检测的对象(数组),当检测的对象状态发生改变,就会触发函数
        // 不填写第二参数时,检测所有元素,相当于 componentDidUpdate生命周期函数
        React.useEffect(()=>{
            // console.log("asdf")
            let timer = setInterval(()=>{
                setCount(count=>count+1);
            },1000);
            return ()=>{        // 在 useEffect中的函数体里返回的函数,相当于 componentWillUnmount生命周期函数
                console.log("unmount")
                clearInterval(timer);
    ;        }
        },[]) // 数组为空是谁也不检测,只执行一次函数,相当于生命周期函数的 componentDidMount
        return (
            <div>
                <h2>当前求和:{count}</h2>
                <button onClick={add}>点击加一</button>
                <h2>当前名字:{name}</h2>
                <button onClick={updName}>修改名字为Jerry</button>
                <button onClick={unmount}>卸载组件</button>
            </div>
        )
    }
    

    可以把 useEffect Hook 看作三个函数的结合:

    • componentDidMount
    • componentDidUpdate
    • componentWillUnmount

    (3) refHook

    refHook 可以让函数式组件实现类似 ref的特性

    export default function Demo() {
        const [count, setCount] = React.useState(0);
        function add(){
            setCount(count=> count+1);
        }
        function show(){
            // 获取文本框内容
            alert(myInput.current.value);
        }
        // 生成一个容器
        const myInput = React.useRef();
        return (
            <div>
                <h2>当前求和:{count}</h2>
                {/* 绑定容器 */}
                <input type="text" ref={myInput}/>
                <button onClick={add}>点击加一</button>
                <button onClick={show}>点击展示数据</button>
            </div>
        )
    }
    

    这个就没啥难度。

    3. Fragment

    react渲染组件的时候,当你的组件越来越多时,你有没有发现你的 DOM层级越来越多,有些不美观,所以就出现了 Fragment
    可以使用 Fragment标签代替组件的根标签,在 React解析的时候会被处理掉, 从而让生成出来的代码的层级更加简洁。

    export default class Demo extends Component {
        render() {
            return (
                // 使用空标签可以达到一样的效果,但是空标签不允许包含任何的属性
                <Fragment key={1}>
                    <input type="text"/>
                    <input type="text"/>
                </Fragment>
            )
        }
    }
    

    4. Context

    Context是一种新的组件通信方式,常用于【祖组件】和【后代组件】之间通信。

    // 1. 创建一个 Context容器对象
    const UserNameContext = React.createContext();
    // 1.1 拿到 Provider与 Consumer属性
    const {Provider, Consumer} = UserNameContext;
    export default class A extends Component {
        state={username: "Tom"}
        render() {
            return (
                <div className="a">
                    <h1>我是A组件</h1>
                    <p>我的用户名是:{this.state.username}</p>
                    {/* 2 使用组件,后代组件都能收到来自 value的值,就在 this上的 context属性上(需提前声明)  */}
                    <Provider value={this.state.username}>
                        <B/>
                    </Provider>
                </div>
            )
        }
    }
    class B extends Component {
        // 3. 声明接受 Context
        static contextType = UserNameContext;   // 此方法只适用于 类组件
        render() {
            console.log(this.context);  // Tom
            return (
                <div className="b">
                    <h2>我是B组件</h2>
                    <p>A的用户名是:{this.context}</p>
                    <C/>
                </div>
            )
        }
    }
    function C() {
        return (
            <div>
                 <div className="c">
                    <h3>我是C组件</h3>         
                    {/* 3.2 使用 Consumer组件进行声明接受(类组件和函数式组件都可以)  */}
                    <Consumer>
                        {value=> ("A的用户名是:" + value)}
                    </Consumer>
                 </div>
            </div>
        )
    }
    

    5. PureComponent

    在组件中,只要执行了 setState(),即使没更新状态数据,组件也会重新 render()
    只要组件重新 render(),就会自动重新 render()子组件,纵使子组件没有使用到父组件任何数据。
    这两种情况都会导致重复渲染,使得效率低下。

    效率高的做法:只有组件的stateprops发生变化时菜重新render()

    • 解决方法1:通过生命周期函数 shouldComponentUpdate() 进行数据判断在进行重新渲染
    • 解决方法2:类组件通过继承 PureComponent组件,自动进行数据判断(浅对比---判断地址值)(常用)
    import React, { Component, PureComponent } from 'react'
    export default class Parent extends PureComponent {
        state = {carName: "奔驰"}
        changeCar = ()=>{
            this.setState(state=>({carName:"迈巴赫"}));
        }
        // shouldComponentUpdate有两个参数,分别是准备修改的 props和 state
        // shouldComponentUp date(nextProps, nextState){
        //     console.log(nextProps, nextState);  // 目标要修改的props和state
        //     console.log(this.props, this.state);   // 还未修改原本的props和state
        //     return !this.state.carName === nextState.carName;
        // }
        render() {
            console.log("parent render");
            return (
                <div className="parent">
                    <h1>Parent</h1>
                    <p>我的车是:{this.state.carName}</p>
                    <button onClick={this.changeCar}>点击换车</button>
                    <Child/>
                </div>
            )
        }
    }
    class Child extends PureComponent {
        
        // shouldComponentUpdate(nextProps){
        //     return !this.props.carName === nextProps.carName
        // }
        render() {
            console.log("child render");
            return (
                <div className="child">
                    <h2>Child</h2>
                    {/* <p>父亲的车是:{this.props.carName}</p> */}
                </div>
            )
        }
    }
    

    6. 父子组件

    不多说,直接上代码:

    import React, { Component, PureComponent } from 'react'
    import "./index.css"
    
    export default class Parent extends PureComponent {
        state = {carName: "奔驰"}
        changeCar = ()=>{
            this.setState(state=>({carName:"迈巴赫"}));
        }
        render() {
            console.log("parent render");
            return (
                <div className="parent">
                    <h1>Parent</h1>
                    <p>我的车是:{this.state.carName}</p>
                    {/* A组件 与 B组件 形成父子组件的第二种方法 */}
                    {/* <A>
                        <B/>
                    </A> */}
                    {/* 类似于 Vue的插槽 */}
                    <A render={(name)=><B name={name}/>}/>  
                </div>
            )
        }
    }
    
    class A extends PureComponent {
        render() {
            console.log("A render");
            return (
                <div className="a">
                    <h2>A</h2>
                    {/* A组件 与 B组件 形成父子组件的第一种方式 */}
                    {/* <B/> */}
                    {/* {this.props.children} */}
                    {this.props.render("Tom")}
                </div>
            )
        }
    }
    class B extends PureComponent {
        render() {
            console.log("B render");
            return (
                <div className="b">
                    <h2>B</h2>
                    <p>{this.props.name}</p>
                </div>
            )
        }
    }
    

    7. ErrorBoundary

    当你的组件存在父子组件关系时,如果说你的子组件出现了错误,那么会导致父组件一并崩掉,那有没有什么办法,可以把错误控制在一个组件里,不让他扩散呢?
    答案是有的,有两个函数:

    • getDerivedStateFromError(error)
    • componentDidCatch(error, info)

    上代码:

    import React, { Component, Fragment } from 'react'
    import Child from "./Child";
    // 错误边界即把组件的错误信息控制在一个组件中,不使他扩散而导致程序崩溃
    export default class Person extends Component {
        state = {
            hasError: "",   // 用于标识子组件是否产生错误
        }
        // 当子组件发生错误时会触发该生命周期函数,且参数为错误信息
        // 只适用于生产环境,只能捕获后代组件生命周期产生的错误
        static getDerivedStateFromError(error){
            // 一般用于处理错误出现时返回给用户展示的东西
            console.log("出错了");
            console.log(error);
        }
        // 组件渲染过程中出错就会触发该生命周期函数
        componentDidCatch(error, info){
            // 一般用于统计错误,反馈给雾浮起,用于通知程序员进行bug修改
            console.log("渲染组件出错");
            console.log(error, info)
        }
        render() {
            return (
                <Fragment>
                    <h2>我是Parent组件</h2>
                    {
                        this.state.hasError ? 
                            <h2>当前网络不大行,建议买高级网络套餐好吧</h2> :
                            <Child/>                
                    }
                </Fragment>
            )
        }
    }
    

    8. 组件通信方式总结

    • 组件之间的关系:
      1. 父子组件
      2. 兄弟组件
      3. 祖孙组件(跨级组件)
    • 几种通信方式:
      1. props
        1. children props
        2. render props
      2. 消息订阅 pubsub, event
      3. 集中式管理 redux, dva, react-redux
      4. conText 生产者, 消费者模式
    • 较好的搭配方式:
      • 父子组件:props
      • 兄弟组件: 消息订阅发布,集中式管理
      • 祖孙组件:消息订阅发布,集中式管理,conText

    十三、最后

    这些就是我学习React的一些学习笔记了,日后如果还有其他的内容的话应该会深究后写成单独的文章了。


    起源地下载网 » React快速暴力入门

    常见问题FAQ

    免费下载或者VIP会员专享资源能否直接商用?
    本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
    提示下载完但解压或打开不了?
    最常见的情况是下载不完整: 可对比下载完压缩包的与网盘上的容量,若小于网盘提示的容量则是这个原因。这是浏览器下载的bug,建议用百度网盘软件或迅雷下载。若排除这种情况,可在对应资源底部留言,或 联络我们.。
    找不到素材资源介绍文章里的示例图片?
    对于PPT,KEY,Mockups,APP,网页模版等类型的素材,文章内用于介绍的图片通常并不包含在对应可供下载素材包内。这些相关商业图片需另外购买,且本站不负责(也没有办法)找到出处。 同样地一些字体文件也是这种情况,但部分素材会在素材包内有一份字体下载链接清单。
    模板不会安装或需要功能定制以及二次开发?
    请QQ联系我们

    发表评论

    还没有评论,快来抢沙发吧!

    如需帝国cms功能定制以及二次开发请联系我们

    联系作者

    请选择支付方式

    ×
    迅虎支付宝
    迅虎微信
    支付宝当面付
    余额支付
    ×
    微信扫码支付 0 元