看 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')
)