史上最全面的React-react基础

2,894 阅读26分钟

1、为什么组件化

什么是react

  • React 是一个用于构建用户界面的JavaScript库

  • 核心专注于视图,目的实现组件化开发

组件化

的概念 我们可以很直观的将一个复杂的页面分割成若干个独立组件,每个组件包含自己的逻辑和样式 再将这些独立组件组合完成一个复杂的页面。 这样既减少了逻辑复杂度,又实现了代码的重用

  • 可组合:一个组件可以和其他的组件一起使用或者可以直接嵌套在另一个组件内部

  • 可重用:每个组件都是具有独立功能的,它可以被使用在多个场景中

  • 可维护:每个小的组件仅仅包含自身的逻辑,更容易被理解和维护

自己实现一个组件化 — 开关例子

// 原生js实现 

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Switch</title>
</head>
<body>
    <div class="app">
        <div>
            <input type="checkbox" class="switch">
            <p class="text">关</p>
        </div>
        <div>
            <input type="checkbox" class="switch">
            <p class="text">关</p>
        </div>
    </div>
    <script>
        let btn = document.querySelector(".switch");
        let text = document.querySelector(".text");
        btn.addEventListener('change',function (e) {
            text.innerHTML = e.target.checked?'开':'关'
        },false)
    </script>
    <style>
        .switch{
            -webkit-appearance: none;
            width: 50px;
            border: 1px solid #dfdfdf;
            border-radius: 30px;
            height: 32px;
            position: relative;
            outline: none;
            /*background: #7264ff;*/
            transition: all 0.2s linear;
        }
        .switch:checked{
            box-shadow:#7264ff  0 0 16px 16px inset;
            transition: all 0.2s linear;
        }
        .switch:before{
            content: '';
            position: absolute;
            left: 0;
            top:0;
            width: 30px;
            height: 30px;
            border-radius: 50%;
            box-shadow: 1px 1px 2px 0 #dfdfdf;
            background: #ffffff;
            transition:all 0.2s linear;
        }
        .switch:checked:before{
            left: 20px;
            transition:all 0.2s linear;
        }
    </style>
</body>
</html>

由于上面的例子:js和html不能复用,所以创建字符串(复用html结构)

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Switch</title>
</head>
<body>
<div class="app">

</div>
<script>

    //  创建字符串(复用html结构)
    class Switch{
        render(){
            return (`
                <div>
                     <input type="checkbox" class="switch">
                     <p class="text">关</p>
                </div>
            `)
        }
    }

    let app = document.querySelector('.app');
    app.innerHTML = new Switch().render();
    app.innerHTML += new Switch().render();

</script>
<style>...</style>
</body>
</html>

但是,字符串不能绑定事件,所以我们要将字符串变换成dom元素

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Switch</title>
</head>
<body>
<div class="app">

</div>
<script>

    // 将字符串变换成dom元素
    class Switch{
    
    
        // 申明一个私有状态 控制开关
        constructor(){
            this.state = {turn:false}
        }
        
        
         // 创建dom元素  接收参数是字符串,将字符串转成dom
        createDOMFromString(str){
            let oDiv = document.createElement('div');
            oDiv.innerHTML = str;
            // 获取第一个儿子节点
            return oDiv.firstElementChild;
        }
        
        
        // 改变状态,从新渲染dom 
        setState(newState){
            // Object.assign:后面的覆盖前面的
            Object.assign(this.state,newState);
            // this.state={...this.state,...{turn:!this.state.trun}};
            let oldEl = this.el;
            let newEl = this.render();// 渲染一个新的元素
            oldEl.parentNode.replaceChild(newEl,oldEl); // 将老的替换成新的
        }
        
        
        Change(){ // this就是实例
            // console.log(this)
         this.setState({turn:!this.state.turn});
        }
        
        
        render(){
            this.el = this.createDOMFromString(`
                <div>
                     <input type="checkbox" class="switch"
                     ${this.state.turn?'checked':''}>
                     <p class="text">${this.state.turn?'开':'关'}</p>
                </div>
            `);
            this.el.firstElementChild.addEventListener('change',this.Change.bind(this),
            false);
            //  这时这个el就是个dom元素  可以绑事件
            return this.el;
        }
    }


    let app = document.querySelector('.app');
    app.appendChild(new Switch().render());
    app.appendChild(new Switch().render());


</script>
<style>...</style>
</body>
</html>

封装方法,将公用的方法提取出来

<script>

    class Component{

        createDOMFromString(str){
            let oDiv = document.createElement('div');
            oDiv.innerHTML = str;
            return oDiv.firstElementChild;
        }

        // 如果想渲染dom  可以通过setState
        setState(newState){
            Object.assign(this.state,newState);
            let oldEl = this.el;
            let newEl = this._render();
            oldEl.parentNode.replaceChild(newEl,oldEl);
        }

        _render(){
            // this是new Switch()实例,this.render()拿到字符串
            this.el = this.createDOMFromString(this.render());
            this.el.firstElementChild.addEventListener('change',this.Change.bind(this),false);
            return this.el;
        }

        // 把元素添加到页面上
        mount(container){
            container.appendChild(this._render());
        }

    }

    // 继承Component
    class Switch extends Component{

        constructor(){
            super();
            this.state = {turn:false}
        }

        Change(){ // this就是实例
            setTimeout(()=>{
                this.setState({turn:!this.state.turn});
            },300)
        }

        render(){
            return `
                    <div>
                         <input type="checkbox" class="switch"
                         ${this.state.turn?'checked':''}>
                         <p class="text">${this.state.turn?'开':'关'}</p>
                    </div>
                `
        }

    }

    let app = document.querySelector('.app');
    // app.appendChild(new Switch()._render());
    // app.appendChild(new Switch()._render());


     // 模拟实现了一个渲染的render方法
     // new Switch().mount(app);
    let render = (ele,container)=>{
        ele.mount(container);
    };
    render(new Switch(),app);
    render(new Switch(),app);

</script>

2、createElement+render

创建react项目

生成react项目的create-react-app,只在命令行里用的 -g

    npm install create-react-app -g
    create-react-app <project-name>

打开项目,生成的项目可以自动监控改动进行刷新

cd <project-name>
npm strat

默认会自动安装React,react由两部分组成,分别是:

  • react.js 是 React 的核心库

  • react-dom.js 是提供与DOM相关的功能,会在window下增加ReactDOM属性,内部比较重要的方法是render,将react元素或者react组件插入到页面中。

配置

eslint,jshint关闭验证代码是否符合规范,在default setting中设置

js版本es6 选择React jsx ——> 在default setting中设置找到language

环境作用

public下有一个index.html

src下要保证有一个index.js

最后会将文件打包到html中

写个hello world

react有两部分组成,一个叫react包,react-dom,语法都是es6

import语法要放置到页面最顶部

ReactDOM中就一个方法比较常用 叫render

react元素,JSX元素 javascript+xml html也是xml的一种 javascript+html

jsx的html部分和原生html"基本"一样,不是完全一样

// index.js

import React from 'react';
// {render} 将ReactDOM.render()进行解构后可直接使用render()
import ReactDOM,{render} from 'react-dom';


// react有两部分组成,一个叫react包,react-dom,语法都是es6
// import语法要放置到页面最顶部
// ReactDOM中就一个方法比较常用  叫render

// react元素,JSX元素  javascript+xml  html也是xml的一种  javascript+html
// jsx  html部分和原生html"基本"一样,不是完全一样
render(<h1>hello world</h1>,document.getElementById('root'));

jsx是一个语法糖,babeljs.io这个网站可以进行转义

简介JSX

是一种JS和HTML混合的语法,将组件的结构、数据甚至样式都聚合在一起定义组件,会编译成普通的Javascript。

需要注意的是JSX并不是html,在JSX中属性不能包含关键字,像class需要写成className,for需要写成htmlFor,并且属性名需要采用驼峰命名法!

createElement

jsx元素->React.createElement->虚拟dom对象->render方法

import React from 'react';
import ReactDOM,{render} from 'react-dom';


// 1、JSX其实只是一种语法糖,最终会通过babel转译成createElement语法
// React.createElement(
//     'h1',
//     { className: 'red' },
//     '\u55E8\uFF0C',
//     React.createElement(
//         'span',
//         { id: 'handsome' },
//         '\u7F8E\u5973'
//     )
// );


console.log(<h1 className='red'>嗨,<span id='handsome'>美女</span></h1>);
// 2、将react元素转化成一个对象"虚拟dom"  {type:"h1",props:{className:"red",children:[嗨,
// {type:"span",props:{id:"handsome",children:"帅哥"}]}}


// 3、通过render方法渲染出一个对象,真实dom
render(<h1 className='red'>嗨,<span id='handsome'>美女</span></h1>,
    document.getElementById('root'));

createElement简单实现代码

// react元素/JSX元素
function ReactElement(type,props){ // type,props
    this.type = type;
    this.props = props;
}

function createElement(type,props,...children){
    if(children.length === 1) children = children[0];
    // children要放到props里面去
    return new ReactElement(type,{...props,children:children});
}

//  模拟render实现
// {type:"h1",props:{className:"red",children:[嗨,
// {type:"span",props:{id:"handsome",children:"帅哥"}]}}
let myRender = (obj,container) =>{
    let {type,props} = obj;
    let ele = document.createElement(type);// 创建第一层

    // key代表className和children
    for(let key in props){
        if(key === 'children'){
            // children有可能是数组  也可能是一个
            if(typeof props[key] === 'object'){// 数组 ["姜,",{type:'',props:{}}]
                props[key].forEach(item=>{
                    if(typeof item === 'object'){
                        // 如果子元素是对象那就继续调用myRender
                        myRender(item,ele)
                    }else{
                        ele.appendChild(document.createTextNode(item));
                    }
                })
            }else{ // 一个的话直接插入到h1中
                ele.appendChild(document.createTextNode(props[key]));
            }
        }else if(key === 'className'){
            ele.setAttribute('class',props[key]);
        }else{
            ele.setAttribute(key,props[key]);
        }
    }
    container.appendChild(ele);// 将元素插入到页面中
};


// 将我们自己创建出来的"元素"对象  插入到root中
myRender(createElement(
        'h1',
        { className: 'red' },
        '\u55E8\uFF0C',
        createElement(
            'span',
            { id: 'handsome' },
            '\u7F8E\u5973'
        )
    ),document.getElementById('root'));

3、组件

jsx的语法规则

1、在react中想将js当作变量引入到jsx中需要使用{}

import React,{Component} from 'react';
import ReactDOM from 'react-dom';

let str = '哈喽';
let el = <span>{str}</span>;
ReactDOM.render(el,document.getElementById('root'));

2、在jsx中,相邻的两个jsx元素 ,渲染时需要外面包裹着一层元素

3、{}取值表达式,取的是有返回值的结果, 可以放JS的执行结果

4、如果多个元素想在return后面换行,我们需要加一个()当作整体返回

5、<{来判断当前是html还是js

import React,{Component} from 'react';
import ReactDOM from 'react-dom';

function  build(str) {
    return (
        <div>
            {/*这是注释*/}
            <h1>{str.name}建立学校</h1>
            <h1>{str.name}建立学校</h1>
        </div>
    )
}
// let el = <div>{build('哈喽')}</div>;
let el = <div>{build({name:'哈喽'})}</div>;
ReactDOM.render(el,document.getElementById('root'));

循环数组的列子

循环时需要带key属性

import React,{Component} from 'react';
import ReactDOM from 'react-dom';

let lessons = [
    {name:'vue',price:800},
    {name:'react',price:1000},
];

function toLesson(item) {
    return `当前课程是${item.name} 价格是${item.price}`
}

// 数组方法 find map filter reduce
let ele = (
    <ul>
        {lessons.map((item,index)=>(
            // null在react中也是一个合法的元素 表示不存在,没有
            item.price<1000?null:<li key={index}>{toLesson(item)}</li>
        ))}
    </ul>
);

ReactDOM.render(ele,window.root);
// window.root 直接取id

jsx中的属性规则

  • 在JSX中分为普通属性和特殊属性,像class要写成className,for要写成htmlFor

  • style要采用对象的方式

  • dangerouslyInnerHTML插入html(xss攻击,基本上用不到)

import React,{Component} from 'react';
import ReactDOM,{render} from 'react-dom';

let str = '<h1>纯标签</h1>';
let styl = {backgroundColor:'red'};
render(<ul>
    <li id='cc' className='aa bb'></li>
    <li htmlFor="aa" style={styl}></li>
    <li dangerouslySetInnerHTML={{__html:str}}></li>
</ul>,window.root)

react组件的特点声明方式

react元素是是组件组成的基本单位

  • 首字母必须大写,目的是为了和JSX元素进行区分

  • 组件定义后可以像JSX元素一样进行使用

  • 每个组件必须返回唯一的顶级JSX元素

  • 可以通过render方法将组件渲染成真实DOM

组件的两种定义方式

react怎么区分是组件还是jsx元素?组件名需要开头大写,react组件当作jsx来进行使用

  • 第一种方式是函数声明
import React,{Component} from 'react';
import ReactDOM,{render} from 'react-dom';
let school1 = {name:'张三',age:8};
let school2 = {name:'李四',age:0};

function Build(props) {
    return <p>{props.name}{props.age}</p>
}

render(<div>
    {/*<Build name={school1.name} age={school1.age}/>*/}
    {/*<Build name={school2.name} age={school2.age}/>*/}
    {/*将对象中的内容解构出来传递给Build组件  不用一个个取出来传递*/}
    <Build name={...school1}/>
    <Build name={...school2}/>
</div>,window.root)
  • 第二种方式是类声明
import React,{Component} from 'react';
import ReactDOM,{render} from 'react-dom';
import PropTypes from 'prop-types';// 属性校验的包



// 1、属性是由外界传递的,外面不能改属性,只有状态是属于组件自己的

class School extends  Component{ // Component提供了列如this.setState()方法
    static propType = { // 静态属性 es7,es6只支持静态函数
        age:PropTypes.string
    };
    static defaultProps = { // 校验默认属性
        age:'100'
    };
    // 类上有自己的构造函数constructor(),super()子类继承父类私有属性,extends继承的是公有
    constructor(props){
        super(props);
    }
    render(){ // render组件长什么样子,render的返回值只能有一个根元素
        return(
            // 通过{}取值不能打印对象  可以转化成字符串打印
            <div>
                {JSON.stringify(this.props)}
            </div>
        )
    }
}
// School.prototype = {age:PropTypes.string};
// render将虚拟dom装换成真实dom
render(<School name={'珠峰'} age={8}/>,window.root);

声明组件的方式有两种:函数声明(名字开头必须大写,没有生命周期、状态、this)和类声明(有生命周期: componentDidMount 渲染完成,componentWillUnmount 组件将要卸载,有状态和this)

4、属性应用

组件中属性和状态的区别

组件的数据来源有两个地方

  • props 外界传递过来的(默认属性,属性校验)

  • state 状态是自己的,改变状态唯一的方式就是setState

属性和状态的变化都会影响视图更新

改变state状态例子

import React,{Component} from 'react';
import ReactDOM,{render} from 'react-dom';

class Counter extends  Component{
    constructor(){
        super();
        this.state = {count:{number:1}} // state 状态是自己的
    };
    handleClick = () =>{
        this.setState({ // 改变状态唯一的方式就是setState
            count:{number:this.state.count.number+1}
        });
    };
    render(){
        return(
            <p>
                {this.state.count.number}
                <button onClick={this.handleClick}>+</button>
            </p>
        )
    }
}

// 调用了两次counter组件 复合组件
ReactDOM.render(<div>
    <Counter/>
    <Counter/>
</div>,window.root);
  • 什么是复合组件,就是将多个组件进行组合,例如调用了两次counter组件(见上面代码列子)

  • 解构非常复杂时可以把组件分离(见下面代码例子)

  • 复合组件,有父子关系,父的数据传递给子的数据(见下面代码列子)

通过属性传递数据例子 => 父传子

实现一个Panel组件, Panel(父组件) Header(子组件) Body(子组件) 三个组件

项目下安装bootstrap yarn add bootstrap,import引入css

yarn需要先npm i yarn -g全局安装一下

import React,{Component} from 'react';
import ReactDOM,{render} from 'react-dom';
import 'bootstrap/dist/css/bootstrap.css';


// react是单向数据流  数据传递是先通过属性传递传给父,再通过属性传递由父组件传给子组件

class Panel extends  Component{
    render(){
        let {header,body} = this.props;
        return(
            <div className='container' style={{color:'red'}}>
                <div className='panel-default panel'>
                    <Header header={header}/>
                    <Body body={body}/>
                </div>
            </div>
        )
    }
}


class Header extends Component{
   render(){
       return(
           <div className='panel-heading'>{this.props.header}</div>
       )
   }
}


class Body extends Component{
    render(){
        return(
            <div className='panel-body'>{this.props.body}</div>
        )
    }
}


let data = {header:'我非常帅',body:'长的帅'};
ReactDOM.render(<Panel {...data}/>,window.root);
  • 父传子,通过属性传递值(上诉例子)

  • 子传父,父亲通过属性change传递给儿子一个函数,儿子调用父亲的函数,将值传递给父亲,父亲再更新值,刷新视图(例子如下)

子传父

import React,{Component} from 'react';
import ReactDOM,{render} from 'react-dom';
import 'bootstrap/dist/css/bootstrap.css';

class Panel extends  Component{
    constructor(){
        super();
        this.state = {color:'primary'}
    }
    changColor = (color) =>{// 到时候儿子传递一个颜色
        this.setState({color:color});
    }
    render(){
        return(
            <div className='container' style={{color:'red'}}>
                <div className={'panel-'+this.state.color+' panel'}>
                    <Header
                        header={this.props.header}
                        change={this.changColor}
                    />
                </div>
            </div>
        )
    }
}


class Header extends Component{
    handleClick = () =>{
        this.props.change('danger');
    }
    render(){
        return(
            <div className='panel-heading'>
                {this.props.header}
                <button 
                    className='btn btn-danger' 
                    onClick={this.handleClick}>
                    改颜色
                </button>
            </div>
        )
    }
}


let data = {header:'我非常帅'};
ReactDOM.render(<Panel {...data}/>,window.root);

5、受控组件和非受控组件

受控组件(受状态控制)和非受控组件

受控组件

  • 受状态金控制的组件,必须要有onChange方法,否则不能使用

  • 受控组件可以赋予默认值(官方推荐使用:受控组件)

受控组件-输入框例子1

输入框和视图用同一个数据(这个数据不能是外界传过来的,是内部自己的,声明一个私有状态 ),用于视图和输入框,这样输入框改视图就刷新

import React,{Component} from 'react';
import ReactDOM,{render} from 'react-dom';

// 输入框和视图用同一个数据(这个数据不能是外界传过来的,是内部自己的,声明一个私有状态
// ),用于视图和输入框,这样输入框改视图就刷新

class Input extends Component{

    constructor(){
        super();
        this.state = {val:'100'}
    }
    
    handleChange = (e) =>{//e是事件源,e.target获取元素
        let val = e.target.value;//获取到输入框的值
        this.setState({val});
    }
    
    render(){
        return(
            <div>
                <input type="text" value={this.state.val}
                onChange={this.handleChange}/>
                {this.state.val}
            </div>
        )
    }
    
}

ReactDOM.render(<Input/>,window.root);

受控组件-输入框例子2-求和

输入框value值和视图用同一个数据,由于这个数据不能是外界传递过来的,所以只能声明一个私有状态state,通过函数handleChange(key,e)去改变这个状态,key表示的就是当前状态改的是哪一个,e表示的是事件源

import React,{Component} from 'react';
import ReactDOM,{render} from 'react-dom';

class Sum extends Component{

    constructor(){
        super();
        this.state = {a:1,b:1};
    }
    
    // key表示的就是当前状态改的是哪一个,e表示的是事件源
    handleChange(key,e){// 处理多个输入框的值映射到状态的方法
        // key写在setState中表示是对象的key,对象里只有a,b。所以[key]相当于一个变量会把key
        // 对应的值a和b取出来
        this.setState({
            [key]:e.target.value
        })
    }
    
    render(){
        return(
            <div>
                <input type="text"
                       value={this.state.a}
                       onChange={(e)=>{this.handleChange("a",e)}}
                />
                <input type="text"
                       value={this.state.b}
                       onChange={e=>{this.handleChange("b",e)}}
                />
                {parseInt(this.state.a)+ parseInt(this.state.b)}
            </div>
        )
    }
}

ReactDOM.render(<Sum/>,window.root);

非受控组件-输入框列子-求和

不受受状态控制的组件,不可以赋予默认值,通过ref设置的属性,可以通过this.refs获取到对应的dom元素

import React,{Component} from 'react';
import ReactDOM,{render} from 'react-dom';

// 输入框value值不受状态控制,不能初始化默认值
class Sum extends Component{

    constructor(){
        super();
        this.state = {result:''}
    }
    
    // 通过ref设置的属性  可以通过this.refs获取到对应的dom元素
    handleChange = () =>{
        let result = this.refs.a.value + this.b.value;
        this.setState({result});
    };
    
    render(){
        return(
            <div onChange={this.handleChange}>
                <input type="text" ref="a"/>
                {/*x代表的是真实的dom,把元素挂载在了当前实例上,上面好能拿到这个dom元素*/}
                <input type="text" ref={ x => this.b = x }/>
                {this.state.result}
            </div>
        )
    }
    
}

ReactDOM.render(<Sum/>,window.root);

6、搜索框

环境安装

1、https://www.cnblogs.com/whycxb/p/7126116.html 谷歌浏览器中安装JsonView扩展程序,方便查看jsonp,抓包地址

2、https://github.com/kugouxiaohuang/react-developer-tools-chrome安装react-developer-tools-chrome

3、需要安装jsonp(通过jsonp这个包去发送请求实现跨域) https://github.com/webmodules/jsonp,jsonp安装使用

yarn add jsonp

百度搜索框例子

import React,{Component} from 'react';
import ReactDOM,{render} from 'react-dom';
import 'bootstrap/dist/css/bootstrap.css';
import oldJSONP from 'jsonp';


// jsonp不支持promise,所以写个新的方法  能支持promise也能支持jsonp
function jsonp(url,opts={}) {
    return new Promise((resolve,reject)=>{
        // url是jsonp请求的路径  opts是请求的属性,第三个参数是成功的回调
        oldJSONP(url,opts,function (err,data) {
            if(err)reject(err);
            resolve(data);// 成功调用resolve
        })
    })
}


class Search extends Component{

    constructor(){
        super();
        this.state = {val:'',arr:[],index:-1}//val 是输入框的内容  arr是所有数据列表
    }

    // // 页面加载完后就调这个方法(看效果的)
    // // async + await  await跟的是promise  有await就需要用async来修饰此函数
    // async componentDidMount(){
    //     // let result = await jsonp('https://sp0.baidu.com/5a1Fazu8AA54nxGko9WTAnF6hhy/su?wd=a',{param:'cb'});
    //     let result = await jsonp('https://sp0.baidu.com/5a1Fazu8AA54nxGko9WTAnF6hhy/su?wd='+wd,{param:'cb'});
    //     console.log(result)
    // }


    handleChange = async (e) => {
        let wd = e.target.value;
        this.wd = wd;//保存输入的内容
        // let {s} = result; 这里的s是从jsonp返回值里解构出来的
        // jsonp(url,opts,fn) 百度回调名字叫cb,
        let {s} = await jsonp('https://sp0.baidu.com/5a1Fazu8AA54nxGko9WTAnF6hhy/su?wd='+wd,{param:'cb'});
        this.setState({val:wd,arr:s});
        // console.log(this.state.arr);
    }


    changeIndex = (e) => {
        let index = this.state.index;// 默认的索引
        if(e.keyCode===38 || e.keyCode===40){
            e.preventDefault();//组织默认行为 避免光标错位
            if(e.keyCode===38){
                index--;
                if(index === -2){ // 目前在-1 --变成了-2 回到最后一个
                    index = this.state.arr.length-1;
                }
            }else {
                index++;
                if(index === this.state.arr.length){ //当越界了  回到-1
                    index = -1;
                }
            }
            this.setState({index:index,val:this.state.arr[index] || this.wd });
        }
    }


    enter = (e) =>{
        if(e.keyCode === 13){
            window.open('https://www.baidu.com/s?wd='+this.state.val)
        }
    }


    render(){
        return(
            <div className='container'>
                <div className='panel panel-default'>
                    <div className='panel-heading'>
                        <input type="text"
                               className='form-control'
                               value={this.state.val}
                               onChange={this.handleChange}
                               onKeyDown={this.changeIndex}
                               onKeyUp={this.enter}
                        />
                    </div>
                    <div className='panel-body'>
                        <ul className='list-group'>
                            {this.state.arr.map((item,index)=>{
                                // console.log(this.state.arr);
                                return <li className={(this.state.index === index?'' + 'active':'')+' list-group-item'} key={index}>{item}</li>
                                // map需要返回一个新数组  记得这里要加return
                            })}
                        </ul>
                    </div>
                </div>

            </div>
        )
    }


}
ReactDOM.render(<Search/>,window.root);

7、生命周期(必问必会)

计数器例子

import React,{Component} from 'react';
import ReactDOM,{render} from 'react-dom';


// 生命周期流程图:defaultProps —> constructor —> componentWillMount —> render
//  —> componentDidMount (改状态的时候,会从新渲染)


class Counter extends Component{
    
    
// // PureComponent会比较两个状态相等就不会刷新视图,是浅比较
// class Counter extends React.PureComponent{

    
    // 静态属性
    static defaultProps = {
        name:'珠峰培训'
    }
    
    
    constructor(props){
        super();
        this.state = {number:0}
        console.log('1.constructor构造函数')
    }
    
    
    // 组件将要挂载
    componentWillMount(){ 
        // // localStorage.getIte()取本地的数据
        // // (同步的方式:采用渲染之前获取数据,值只渲染一次)
        // localStorage.getItem('a'); // (同步)
        console.log('2.组件将要加载 componentWillMount');
    }
    
    
    componentDidMount(){
        console.log('4.组件挂载完成 componentDidMount');
    }
    

    //通过handleClick函数改变状态
    handleClick =() => { 
        // this.setState({number:this.state.number});
        this.setState({number:this.state.number+1});
    }

    
    // react可以shouldComponentUpdate方法中优化,或者通过PureComponent(纯组件) 可以帮我们做这件事
    // shouldComponentUpdate是否应该更新,返回boole类型,默认true,如果此函数返回false,就不会调用render方法了
    shouldComponentUpdate(nextProps,nextState){ // 代表的是下一次的属性和下一次的状态
        console.log('5.组件是否跟新 shouldComponentUpdata')
        // console.log(nextState);
        // return false;
        return nextState.number%2; // 奇数跟新偶数不跟新
        // return nextState.number !== this.state.number;//如果状态没有变就不跟新(优化,状态没变继续跟新耗性能)
    }  // 不要随便用setState 可能会死循环


    componentWillUpdate(){
        console.log('6.组件将要跟新 componentWillUpdate')
    }
    
    
    componentDidUpdate(){
        console.log('7.组件完成跟新 componentDidUpdate')
    }
    
    
    render(){
        console.log('3.render');
        return(
            <div>
                <p>{this.state.number}</p>
                <ChildCounter n={this.state.number}/>
                <button onClick={this.handleClick}>+</button>
            </div>
        )
    }
    
    
}


// 再写个子组件  看看属性的变化(以上是状态的变化的生命周期),把父组件的状态变成属性传递给儿子,儿子通过props拿到这个属性
// 先渲染父组件的render,再渲染子组件的render。注意:componentWillMount和componentDidMount只走一次
// 只要是组件就会有生命周期  同样ChildCounter也会有生命周期
class ChildCounter extends Component{
    
    
    componentWillMount(){
        console.log('child componentWillMount')
    }
    
    
    render(){
        console.log('child-render')
        return(
            <div>
                {this.props.n}
            </div>
        )
    }
    
    
    componentDidMount(){
        console.log('child componentDidMount')
    }
    
    
    // 组件将要接收新的属性,参数是最新的属性,第一次不会执行,之后属性更新时才会执行
    componentWillReceiveProps(newProps){
        console.log('child componentWillReceiveProps')
    }
    
    
    shouldComponentUpdate(nextProps,nextState){
        return nextProps.n%3; // 这时候子组件判断接收的属性,是否满足跟新条件 为true则更新
    }
    
    
}


ReactDOM.render(<Counter name='计数器'/>,window.root);

整个流程走势图如下:

首先页面渲染

点按钮,状态+1,满足奇数更新偶数不更新,先渲染父组件的render,再渲染子组件的render,页面中父子组件值都变成1。 注意:componentWillMount和componentDidMount只走一次

再点按钮。状态再+1,这时候值变成了2,但是由于偶数不更新,所以页面中的父子组件值还是是1

再点按钮,这时候值为3,父组件跟新但是子组件接收到新的属性不跟新,所以父组件值为3,子组件值不跟新为1。(依次类推)

8、留言板

页面组件化

评论的面板有多少个组件(尽可能把逻辑拆分,好维护):最外面的容器MessageBox、消息表单MessageForm、消息列表MessageList。消息列表中每条信息数据也可以复用,写成一个组件MessageItem。所以一共四个组件,最后把这四个组件在index.js中进行整合。如图:

代码列子

index.js

import React,{Component} from 'react';
import ReactDOM,{render} from 'react-dom';
import MessageBox from "./MessageBox";
import 'bootstrap/dist/css/bootstrap.css'


// 一般整合的文件  只需要渲染
// 根组件  我们将它进行渲染即可
ReactDOM.render(<MessageBox/>,window.root);

MessageBox.js

  • PureComponent纯组件,比较状态的地址,地址相同不会更新。

  • 通过父组件状态来进行交互

  • 传递给后代组件只能一级一级的传递,不能跨级传递。

  • 子不能改父,所以父亲通过属性add传递给儿子一个函数,儿子调用父亲的函数,将值传递给父亲,父亲再更新值,刷新视图

  • componentWillMount页面一加载获取localStorage数据渲染,避免刷新页面数据也丢失

import React,{Component} from 'react';
import MessageList from "./MessageList";
import MessageForm from "./MessageForm";


// export default导出MessageBox组件,index.js引用并渲染
// export default class MessageBox extends React.Component {

// PureComponent纯组件,比较状态的地址,地址相同不会更新。由于push返回的是一个原数组
// 所以这里如果使用PureComponent,将不会更新渲染,所以这里一般不建议使用push而是最好采用用新的状态
// 覆盖掉老的(也算是优化了 不然调用shouldComponentUpdate(nextProps,nextState)去比较更麻烦)
export default class MessageBox extends React.PureComponent {
    
    
    // 通过父组件状态来进行交互
    constructor(){
        super();
        // this.state = {messages:[{id:1,content:'今天吃药了吗?',auth:'珠峰培训',createAt:Date.now()}]}
        this.state = {messages:[]}
    }

    
    // 根据id进行删除,传递给后代组件只能一级一级的传递,不能跨级传递。这里通过delete,del去传递的
    deleteMessage = (id) => {
        let messages = this.state.messages.filter(item => item.id != id);
        this.setState({ // 重新设置状态,这个方法是异步的
            messages:messages
        })
        // 添加完或者删除完  存下数据
        localStorage.setItem('messages',JSON.stringify(messages));
    }

    
    // 子不能改父,所以父亲通过属性add传递给儿子一个函数,儿子调用父亲的函数,
    // 将值传递给父亲,父亲再更新值,刷新视图
    addMessage = (message) =>{ // message是儿子传递过来的,实现子父传递
        // let messageItem = {...message,id:this.state.messages.length,createAt:Date.now()}
        let messageItem = {...message,id:Math.random(),createAt:Date.now()}
        // this.state.messages.push(messageItem);// 不会更新渲染数据,只有调用setStateat才会更新,push不会改变原有的引用地址
        // this.setState({
        //     messages:this.state.messages
        // });// 放到状态中
        let messages = [...this.state.messages,messageItem];
        this.setState({
            messages
        });// 放到状态中
        // console.log(this.state.messages)
        // 添加完或者删除完  存下数据
        localStorage.setItem('messages',JSON.stringify(messages));
    }

    
    // 1、当页面一加载获取localStorage数据渲染,避免刷新页面数据也丢失
    // 2、生命周期是同步的  在componentWillMount中setState是同步的
    // 3、取localStorage的值时  取到后放在状态中  再进行render,执行一次,因为
    //   willMount中的setState会和this.state状态合并。如果这里是DitMount会渲染两次
    componentWillMount(){
        let messages = JSON.parse(localStorage.getItem('messages')) || [];
        this.setState({messages:messages})
    }


    render(){
        return (
            <div className='container'>
                <div className='row'>
                    <div className='col-md-6 col-md-offset-3'>
                        <div className='panel panel-danger'>
                            <div className='panel-heading'>
                                <h1 className='text-center h3'>留言板</h1>
                            </div>
                            <div className='panel-body'>
                                <MessageList messages={this.state.messages} delete={this.deleteMessage}/>
                            </div>
                            <div className='panel-footer'>
                                <MessageForm add={this.addMessage}/>
                            </div>
                        </div>
                    </div>
                </div>
            </div>
        )
    }
    
    
}

MessageForm.js

  • 通过ref获取input的value值,然后调用父组件通过属性add传递过来的addMessage方法,把获取到的值通过props.add()传递到父组件,父组件再通过addMessage方法去从新设置状态值(setState),页面就会从新渲染(只有当页面状态改变或者属性改变时,页面才会从新渲染)
import React,{Component} from 'react';


export default class MessageForm extends React.Component {
    
    
    handleSubmit = (e) =>{
        e.preventDefault();//阻止默认行为,这里是阻止表单的提交
        let message = {auth:this.auth.value,content:this.content.value};
        // console.log(message);// 看拿到这个值没有
        this.props.add(message);// 调用父组件的方法  将值传递到父组件中
    }
    
    
    render(){
        return (
            <form action="form" onSubmit={this.handleSubmit}>
                <div className='form-group'>
                    <label htmlFor="auth" className='control-label'>留言人</label>
                    <input type="text" id='auth' className='form-control' ref = {x=>this.auth=x} required={true}/>
                </div>
                <div className='form-group'>
                    <label htmlFor="content" className='control-label'>内容</label>
                    <textarea id="content" cols="30" rows="10" className='form-control' ref = {x=>this.content=x} required={true}></textarea>
                </div>
                <div className='form-group'>
                    <button className='btn btn-info' type='submit'>留言</button>
                </div>
            </form>
        )
    }
    
    
}

MessageList.js

import React,{Component} from 'react';
import MessageItem from "./MessageItem";


export default class MessageList extends React.Component {
    render(){
        return (
            <ul className='list-group'>
                {/*item里面有:auth  content  id  createAt*/}
                {this.props.messages.map((item,index)=>(
                  <MessageItem key={index} {...item} del={this.props.delete}/>
                ))}
            </ul>
        )
    }
}

MessageItem.js

import React,{Component} from 'react';
export default class MessageItem extends React.Component {
    render(){
        let {auth,id,createAt,content} = this.props;
        return (
            <li className='list-group-item'>
                留言人:{auth} 内容:{content}
                <button className='btn btn-danger btn-xs pull-right' onClick={()=>{this.props.del(id);}}>&times;</button>
                {/*react{}里不能放对象,要转成数组或者字符串*/}
                <span className='pull-right'>时间:{new Date(createAt).toLocaleString()}</span>
            </li>
        )
    }
}

整个目录

9、react环境配置

安装webpack

yarn init -y

yarn add webpack webpack-dev-server babel-core babel-loader babel-preset-es2015
babel-preset-stage-0 babel-preset-react less-loader less css-loader style-loader
file-loader url-loader html-webpack-plugin --dev

yarn add react react-dom

文件配置

index.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
    <div id="root"></div>
</body>
</html>

webpack.config.js

let HtmlWebpackPlugin = require('html-webpack-plugin');
module.exports = {
    entry:'./src/index.js',// 入口
    output:{
        filename:'build.js',// 打包后的文件名
        path:require('path').resolve('./dist')
    },
    module:{
        rules:[
            {test:/\.js$/,use:'babel-loader',exclude:/node_modules/},
            {test:/\.css$/,use:['style-loader','css-loader']},
            {test:/\.less$/,use:['style-loader','css-loader','less-loader']},
            {test:/\.(png|jpg|gif)$/,use:'url-loader'}
        ]
    },
    plugins:[
        new HtmlWebpackPlugin({
            template:'./index.html'
        })
    ]
}

babelrc.json

{
  "presets":["es2015","stage-0","react"]
}

package.json

{
  "name": "slider",
  "version": "1.0.0",
  "main": "index.js",
  "license": "MIT",
  "devDependencies": {
    "babel-core": "^6.26.3",
    "babel-loader": "^7.1.5",
    "babel-preset-es2015": "^6.24.1",
    "babel-preset-react": "^6.24.1",
    "babel-preset-stage-0": "^6.24.1",
    "css-loader": "^1.0.0",
    "file-loader": "^1.1.11",
    "html-webpack-plugin": "^3.2.0",
    "less": "^3.7.1",
    "less-loader": "^4.1.0",
    "style-loader": "^0.21.0",
    "url-loader": "^1.0.1",
    "webpack": "^4.16.0",
    "webpack-dev-server": "^3.1.4"
  },
  "scripts":{
    "dev":"webpack-dev-server",
    "start":"npm run dev",
    "build":"webpack -p"
  },
  "dependencies": {
    "react": "^16.4.1",
    "react-dom": "^16.4.1"
  }
}

index.js

import React,{Component} from 'react';
import ReactDOM,{render} from 'react-dom';

ReactDOM.render('hello world',window.root);

环境配置完成后,我们npm run start

10、react轮播图

目录

index.js

import React,{Component} from 'react';
import ReactDOM,{render} from 'react-dom';
import Slider from "./Slider";
import './index.less'


// 在渲染slider时要提供一些必备的参数
// delay 2秒动一次
// speed 每个0.5s动一张
// dots小点
// arrows箭头
// items几张图片 webpack里面图片不能直接引用字符串,不能打包,所以要先引


import a from './1.jpg';
import b from './2.jpg';
import c from './3.jpg';
let items = [{src:a},{src:b},{src:c}];

ReactDOM.render(<Slider
    delay={2}
    speed={0.5}
    autoplay={true}
    dots={true}
    arrows={true}
    items={items}
/>,window.root);

index.less

*{margin: 0;padding: 0}
ul,li{list-style: none}
.slider-container{
  width:400px;
  height:300px;
  border:5px solid red;
  margin:0 auto;
  position: relative;
  overflow: hidden;
  ul{
    position: absolute;height: 300px;left: 0;top: 0;
    li{
      width: 400px;height: 300px;float: left;
      img{width: 100%;height: 100%;}
    }
  }
  .slider-arrows{
    position: absolute;
    width: 100%;
    height:40px;
    top:50%;
    transform: translateY(-50%);
    left:0;
    span{
      width: 30px;height: 40px;line-height: 40px;display: block;
      background: #ffffff;text-align: center;-webkit-user-select: none;
      cursor: pointer;
      &:nth-child(1){
        float: left;
      }
      &:nth-child(2){
        float: right;
      }
    }
  }
  .slider-dots{
    position: absolute;width: 100%;left: 0;bottom: 20px;text-align: center;
    span{
      background: #ffffff;display: inline-block;width: 20px;height: 20px;
      border-radius: 50%;margin: 3px;cursor: pointer;
      &.active{
        background: rgba(233,222,100,0.8);
      }
    }
  }
}

Slider.js

import React,{Component} from 'react';
import SliderList from "./SliderList";
import SliderArrows from "./SliderArrows";
import SliderDots from "./SliderDots";
export default class Slider extends React.Component {
    constructor(){
        super();
        this.state = {index:0}// 表示当前是第几张
    }
    go =(step)=>{ // 去哪  传入要动几个
        let index = this.state.index+step;// 先加的
        console.log(index);
        if(index === this.props.items.length){ // 当等于最后一张时  越界回到0
            index = 0;
        }
        if(index < 0){// 当小于第一张时  回到最后一张
            console.log("aa")
            index =
                this.props.items.length -1;
        }
        this.setState({
            index:index
        })
    }
    turn = () =>{ // 轮播
        this.timer = setInterval(()=>{
            this.go(1)
        },this.props.delay*1000);
    }
    componentDidMount(){ // 页面加载完成后  看是否需要自动轮播
        if(this.props.autoplay){
          this.turn();
        }
    }
    render(){
        return (
            <div className='slider-container' onMouseEnter={()=>{
                clearInterval(this.timer);
            }} onMouseLeave={()=>{
                this.turn();
            }}>
                <SliderList index={this.state.index} items={this.props.items} speed={this.props.speed}/>
                {this.props.arrows?<SliderArrows go={this.go}/>:null}
                {this.props.dots?<SliderDots go={this.go} items={this.props.items} index={this.state.index}/>:null}
            </div>
        )
    }
}

SliderArrows.js

import React,{Component} from 'react';
export default class SliderArrows extends React.Component {
    render(){
        return (
            <div className='slider-arrows'>
                <span onClick={()=>{this.props.go(-1)}}>&lt;</span>
                <span onClick={()=>{this.props.go(1)}}>&gt;</span>
            </div>
        )
    }
}

SliderDots.js

import React,{Component} from 'react';
export default class SliderDots extends React.Component {
    render(){
        return (
            <div className='slider-dots'>
                {this.props.items.map((item,index)=>(
                   <span key={index} className={this.props.index===index?'active':''}
                         onClick={()=>{this.props.go(index-this.props.index)}}></span>
                ))}
            </div>
        )
    }
}

SliderList.js

import React,{Component} from 'react';
export default class MessageBox extends React.Component {
    render(){
        let style={
            width:this.props.items.length*400+'px', // 设置ul默认宽度
            left:this.props.index*400*-1 + 'px', // 根据当前inedex  移动left值
            transition:`left ${this.props.speed}s linear`
        }
        return (
            <ul style={style}>
                {this.props.items.map((item,index)=>(
                    <li><img src={item.src} key={index}/></li>
                ))}
            </ul>
        )
    }
}

11、react无缝滚动轮播

代码在10、react轮播图基础上有所变动

SliderList.js

import React,{Component} from 'react';
export default class MessageBox extends React.Component {
    render(){
        let style={
            width:(this.props.items.length+1)*400+'px', // 设置ul默认宽度
            left:this.props.index*400*-1 + 'px', // 根据当前inedex  移动left值
            transition:`left ${this.props.speed}s linear`
        }
        return (
            <ul style={style} ref='ul'>
                {this.props.items.map((item,index)=>(
                    <li><img src={item.src} key={index}/></li>
                ))}
                {/*实现无缝轮播要再增加一张图*/}
                <li><img src={this.props.items[0].src} alt=""/></li>
            </ul>
        )
    }
}

Slider.js

import React,{Component} from 'react';
import SliderList from "./SliderList";
import SliderArrows from "./SliderArrows";
import SliderDots from "./SliderDots";
export default class Slider extends React.Component {
    constructor(){
        super();
        this.state = {index:0}// 表示当前是第几张
    }
    go =(step)=>{ // 去哪  传入要动几个
        let index = this.state.index+step;// 先加的
        if(index > this.props.items.length){ // 当等于最后一张时  越界回到0
            this.$ul.style.transitionDuration = '';// 清除ul上的动画
            this.$ul.style.left = 0;// 回到0处
            setTimeout(()=>{// 等动画移除后并且回到了0点  再增加回动画时间(dom刷新一般是30s)
                this.$ul.style.transitionDuration = this.props.speed+'s';// 再增加回来这个动画
                index = 1;// 下一次该走1了
                this.setState({index});
            },30)
            return;//因为设置了setTimeout所以要等待setTimeout后再设置最新状态
        }
        if(index < 0){// 当小于第一张时  回到最后一张
            this.$ul.style.transitionDuration = '';// 清除ul上的动画
            this.$ul.style.left = this.props.items.length*-1*400+'px';
            setTimeout(()=>{
                this.$ul.style.transitionDuration = this.props.speed+'s';
                index = this.props.items.length -1;
                this.setState({index});
            },30);
            return
        }
        this.setState({
            index:index
        })
    }
    turn = () =>{ // 轮播
        this.timer = setInterval(()=>{
            this.go(1)
        },this.props.delay*1000);
    }
    componentDidMount(){ // 页面加载完成后  看是否需要自动轮播
        if(this.props.autoplay){
          this.turn();
        }
        // 通过ulfs获取sliderList中的ul元素(操作dom节点)
        console.log(this.$ul = this.refs.list.refs.ul)
    }
    render(){
        // 越界了并且只是第一个span加上active属性
        return (
            <div className='slider-container' onMouseEnter={()=>{
                clearInterval(this.timer);
            }} onMouseLeave={()=>{
                this.turn();
            }}>
                <SliderList ref='list' index={this.state.index} items={this.props.items} speed={this.props.speed}/>
                {this.props.arrows?<SliderArrows go={this.go}/>:null}
                {this.props.dots?<SliderDots go={this.go} items={this.props.items} index={this.state.index}/>:null}
            </div>
        )
    }
}

SliderDots.js

import React,{Component} from 'react';
export default class SliderDots extends React.Component {
    render(){
        // 越界了并且只是第一个span加上active属性
        return (
            <div className='slider-dots'>
                {this.props.items.map((item,index)=>(
                   <span key={index}
                         className={(this.props.index===index)||(this.props.index===this.props.items.length&&index===0)?'active':''}
                         onClick={()=>{this.props.go(index-this.props.index)}}></span>
                ))}
            </div>
        )
    }
}

React中Children属性

this.props.children是获取组件中间的内容

1、默认不传递是undefined

2、传入一个时是对象类型

3、传入多个就是数组类型

4、我们可以用React.Children.map去遍历this.props.children

import React,{Component} from 'react';
import ReactDOM,{render} from 'react-dom';
// this.props.children是获取组件中间的内容
// 1、默认不传递是undefined
// 2、传入一个时是对象类型
// 3、传入多个就是数组类型
// 4、我们可以用React.Children.map去遍历this.props.children
class Dinner extends Component{
    render(){
        return <div>
            {/*{Object.prototype.toString.call(this.props.children)}*/}
            {React.Children.map(this.props.children,(item,index)=>(
                <li>{item}</li>
            ))}
        </div>
    }
}

ReactDOM.render(<Dinner>
    <div>汉堡</div>
    <div>汉堡</div>
    <div>汉堡</div>
</Dinner>,window.root);