看React文档一天,大致掌握了些基础语法。迫不及待手撸了个Demo。类似于记事本的功能,可以新增、删除、标记项目。基础的一个数据通信更改。

一直技术栈用的是Vue,在写Demo的时候,用的逻辑也是Vue的思想,难免很多地方绕了弯路,只能说用粗暴的方式实现了功能,不然React怎么可能这么复杂呢。

引入文件

<script crossorigin src="https://unpkg.com/react@16/umd/react.production.min.js"></script>
<script crossorigin src="https://unpkg.com/react-dom@16/umd/react-dom.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/babel-core/5.8.23/browser.min.js"></script>

分别引入React文件。由于React无法直接被浏览器识别,所以需要引入Babel文件进行编译。

引入代码核心js

<script type="text/babel" src="./reactList.js"></script>

注意,需要使用Babel编译的文件,需要在加上type="text/babel"。

css样式与root容器

注释部分是最后编译完成后,所需要展示的HTML结构。

<body>
    <div id="root">
        <!-- <div class="input-wrap">
            <input type="text">
            <button>新增</button>
        </div>
        <ul class="list-wrap">
            <li>
                <div>xxxx</div>
                <button>已完成</button>
                <button>删除</button>
            </li>  
        </ul> -->
    </div>
</body>

开始编写reactList.js

界面布局

image.png
如同Vue需要传入el或者mount一个容器一样,React中提供了一个ReactDOM对象,所有顶层API都能在该对象上调用

ReactDOM.rander:在提供的 container 里渲染一个 React 元素,并返回对该组件的引用

rander中有两个参数,一个是需要渲染的内容,一个是内容渲染之后存放的容器

值得注意的是,第一个参数就如同Vue中组件的<template>一样,只能有一个根元素。之后所有的rander渲染的内容同理。

ReactDOM.render(
    <div>
    <div class="input-wrap">
            <input type="text">
            <button>新增</button>
        </div>
        <ul class="list-wrap">
            <li>
                <div>xxxx</div>
                <button>已完成</button>
                <button>删除</button>
            </li>  
        </ul>        
  </div>,
    document.getElementById('root')
)

在React中,对元素的渲染使用了JSX——是一个看起来很像 XML 的 JavaScript 语法扩展。
在Vue中,我们在template中绑定数据,使用的是{{}}, 若是要绑定事件是@xxx, 绑定变量则是:xxx
在JSX中,皆在大括号{}中填写表达式。
React.Component支持用ES6中的Class来定义组件。
我们可以定义一个名为Layout的组件来代替在rander中的HTML

class Layout extends React.Component {
    render() {
        return (
            <div>
                <InputBox />
              <List />
            </div>            
        )
    }
}
class InputBox extends React.Component {
    render() {
        return (
            <div class="input-wrap">
                <input type="text" />
                <button>新增</button>
            </div>            
        )
    }    
}

class List extends React.Component {
    render() {
        return (
            <ul>
                <li>
                    <div>xxxx</div>
                    <button>已完成</button>
                    <button>删除</button>
                </li>
            </ul>
        )
    }
}

ReactDOM.render(
    <Layout/>,
    document.getElementById('root')
)

注意:

1、自定义的组件名与类名大小写需要一致,首字母大写;

2、每个Class中都有一个render返回处理完之后的JSX;

3、return 若是多行代码,需要用();

初始化数据

1、每个Class都有一个constructor构造函数,可以不显式写出来,默认就有,在实例创建的时候初始化数据,只会执行一次。

2、React中的数据都维护在state中,就如同Vue中的data(){}。

3、不同的是,state中的数据不可以直接更改,需要用stateSet方法。

4、stateSet中的数据是直接覆盖,而不是更新。对于复杂的数据结构,需要在外部处理之后再传入stateSet
5、Class中的this指向比较复杂,参考知识库中文章class中this的指向

数据与事件的绑定

与Vue的数据双向绑定不同,React的理念是单向数据流。

父组件通过props传递数据给子组件。

而子组件通过绑定在子组件上的回调函数,来通知父组件。类似于Vue的this.$emit,React中是直接调用父组件通过props传进来的回调函数

class Layout extends React.Component {
    // 为了直观,将多余代码注释
    ...
    render() {
        return (
            <div>
                <InputBox
                  newEvent={this.state.newEvent}
                  addItem={this.addItem}
                />
                <List list={this.state.data}
                  deleteItem={this.deleteItem}
                  finishItem={this.finishItem}
                />                
            </div>
        )
    }  
}

在Layout组件中,分别有两个子组件<InputBox>与<List>。我们统一将数据都维护在<Layout>组件中,当父组件<Layout>中的数据更改之后,子组件中的数据相应发送改变。子组件进行操作之后,将操作的数据通过绑定的回调函数(例如finishItem、deleteItem、addItem)通知父组件处理数据。以此达到兄弟组件之间的交互。

class Layout extends React.Component {
    
    constructor(props) {
        super(props)
        this.state = {
            data: [
                {
                    name: '学react',
                    finish: false
                },
                {
                    name: '学vue',
                    finish: false
                },
                {
                    name: '学javascript',
                    finish: true
                }
            ],
            newEvent: ''
        };

        this.addItem = this.addItem.bind(this)
        this.deleteItem = this.deleteItem.bind(this)
        this.finishItem = this.finishItem.bind(this)
    }  
    // 为了直观,将多余代码注释
    ...
    render() {
        return (
            <div>
                <InputBox
                  newEvent={this.state.newEvent}
                  addItem={this.addItem}
                />
                <List list={this.state.data}
                  deleteItem={this.deleteItem}
                  finishItem={this.finishItem}
                />                
            </div>
        )
    }  
}

在Dom元素上绑定事件的时候,需要绑定事件的执行上下文,或者使用箭头函数。目的就是防止this的隐式丢失。

还是以<Layout>为例,在子组件上绑定的事件,我们都在构造函数中显式绑定了this的指向。

因为函数中this的指向,不取决于作用域,而是取决于执行上下文的this指向。因为点击事件是在Dom上调用的,所以点击时候,函数隐式被绑定window,但是React的类中采用的是严格模式,所以this指向的是undefined。

解决this的指向问题

解决this的指向问题,目前我所知的是四种形式。

1、在构造函数中显式绑定this

class Layout extends React.Component {
    constructor(props) {
        this.finishItem = this.finishItem.bind(this)
    }
    finishItem() {
      console.log(this)
    }
}

2、在Dom的点击事件上绑定this

    render() {
        return (
            <div>
                <InputBox addItem={this.addItem.bind(this)}/>  
            </div>
        )
    }

3、用箭头函数

因为箭头函数的this是在定义函数时绑定的,而不是在执行时候绑定。

    render() {
        return (
            <div>
                <InputBox addItem={() => this.addItem}/>  
            </div>
        )
    }

4、使用React.createClass来定义类

React.createClass与React.Component来创建类,区别还是很大的。可以说React.createClass更加便利,React自身就已经实现了,包括this的绑定。

const Layout = React.createClass({
  render() {
    return (
      <div>
          <InputBox addItem={() => this.addItem}/>  
      </div>
    );
  }
});

代码总览

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <script crossorigin src="https://unpkg.com/react@16/umd/react.production.min.js"></script>
    <script crossorigin src="https://unpkg.com/react-dom@16/umd/react-dom.production.min.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/babel-core/5.8.23/browser.min.js"></script>
    <!-- 操作列表核心逻辑 -->
    <script type="text/babel" src="./reactList.js"></script>
    <title>Document</title>
</head>
<style>
.input-wrap {
    display: flex;
    align-items: center;

}
.input-wrap button {
    margin-left:10px;
}
li {
    display: flex;
    align-items: center;
    margin-bottom: 10px;
}
li button {
    margin-left: 10px;
}
li div {
    width: 100px;
}
.finishItem {
    text-decoration: line-through;
}

</style>
<body>
    <div id="root">
        <!-- <div class="input-wrap">
            <input type="text">
            <button>新增</button>
        </div>
        <ul class="list-wrap">
            <li>
                <div>xxxx</div>
                <button>已完成</button>
                <button>删除</button>
            </li>  
        </ul> -->
    </div>
</body>
</html>
class Layout extends React.Component {
    constructor(props) {
        super(props)
        this.state = {
            data: [
                {
                    name: '学react',
                    finish: false
                },
                {
                    name: '学vue',
                    finish: false
                },
                {
                    name: '学javascript',
                    finish: true
                }
            ],
            newEvent: ''
        };

        this.addItem = this.addItem.bind(this)
        this.deleteItem = this.deleteItem.bind(this)
        this.finishItem = this.finishItem.bind(this)
    }    
    addItem(val) {
        if (!val) {
            alert('请填写项目名称!')
            return
        }
        console.log(this.state.data)
        // 直接覆盖 而不是更新
        const newData = this.state.data.concat({ name: val, finish: false })
        this.setState({
            data: newData
        })
    }
    finishItem(index) {
        console.log(index)
        const list = this.state.data
        list[index].finish = !list[index].finish
        this.setState({
            data: list
        })
    }
    deleteItem(index) {
        console.log(index)
        const list = this.state.data
        list.splice(index, 1)
        this.setState({
            data: list
        })
    }
    render() {
        return (
            <div>
                <InputBox newEvent={this.state.newEvent} addItem={this.addItem}/>
                <List list={this.state.data} deleteItem={this.deleteItem} finishItem={this.finishItem}/>                
            </div>
        )
    }
}

class InputBox extends React.Component {
    constructor(props) {
        super(props)
        this.state = {
            newEvent: props.newEvent
        }
        this.handleChange = this.handleChange.bind(this)
    }
    handleChange(e) {
        this.setState({
            newEvent: e.target.value
        })
    }
    render() {
        return (
            <div class="input-wrap">
                <input type="text" value={this.state.newEvent} onChange={this.handleChange} />
                <button onClick={() => this.props.addItem(this.state.newEvent)}>新增</button>
            </div>            
        )
    }    
}

class List extends React.Component {
    render() {
        return (
            <ul>
                {this.props.list.map((i, index) => {
                    return (
                        <li>
                            <div class={i.finish ? 'finishItem' : ''}>{i.name}</div>
                            <button onClick={() => this.props.finishItem(index)}>{i.finish ? '撤销完成' : '已完成' }</button>
                            <button onClick={() => this.props.deleteItem(index)}>删除</button>
                        </li>                        
                    )
                })}
            </ul>
        )
    }    
}


ReactDOM.render(
    <Layout/>,
    document.getElementById('root')
)

image.png


老虎不长牙
128 声望6 粉丝