初尝React

13次阅读

共计 6673 个字符,预计需要花费 17 分钟才能阅读完成。

看 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

界面布局


如同 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')
)

正文完
 0