看React文档一天,大致掌握了些基础语法。迫不及待手撸了个Demo。类似于记事本的功能,可以新增、删除、标记项目。基础的一个数据通信更改。
一直技术栈用的是Vue,在写Demo的时候,用的逻辑也是Vue的思想,难免很多地方绕了弯路,只能说用粗暴的方式实现了功能,不然React怎么可能这么复杂呢。
引入文件
分别引入React文件。由于React无法直接被浏览器识别,所以需要引入Babel文件进行编译。
引入代码核心js
注意,需要使用Babel编译的文件,需要在加上type="text/babel"。
css样式与root容器
注释部分是最后编译完成后,所需要展示的HTML结构。
开始编写reactList.js
界面布局
如同Vue需要传入el或者mount一个容器一样,React中提供了一个ReactDOM对象,所有顶层API都能在该对象上调用
ReactDOM.rander:在提供的 container 里渲染一个 React 元素,并返回对该组件的引用
rander中有两个参数,一个是需要渲染的内容,一个是内容渲染之后存放的容器
值得注意的是,第一个参数就如同Vue中组件的一样,只能有一个根元素。之后所有的rander渲染的内容同理。
ReactDOM.render(
新增
-
xxxx
已完成
删除
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 (
)
}
}
class InputBox extends React.Component {
render() {
return (
新增
)
}
}
class List extends React.Component {
render() {
return (
-
xxxx
已完成
删除
)
}
}
ReactDOM.render(
,
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 (
newEvent={this.state.newEvent}
addItem={this.addItem}
/>
deleteItem={this.deleteItem}
finishItem={this.finishItem}
/>
)
}
}
在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 (
newEvent={this.state.newEvent}
addItem={this.addItem}
/>
deleteItem={this.deleteItem}
finishItem={this.finishItem}
/>
)
}
}
在Dom元素上绑定事件的时候,需要绑定事件的执行上下文,或者使用箭头函数。目的就是防止this的隐式丢失。
还是以为例,在子组件上绑定的事件,我们都在构造函数中显式绑定了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 (
)
}
3、用箭头函数
因为箭头函数的this是在定义函数时绑定的,而不是在执行时候绑定。
render() {
return (
this.addItem}/>
)
}
4、使用React.createClass来定义类
React.createClass与React.Component来创建类,区别还是很大的。可以说React.createClass更加便利,React自身就已经实现了,包括this的绑定。
const Layout = React.createClass({
render() {
return (
this.addItem}/>
);
}
});
代码总览
Document.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;
}
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 (
)
}
}
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 (
this.props.addItem(this.state.newEvent)}>新增
)
}
}
class List extends React.Component {
render() {
return (
{this.props.list.map((i, index) => {
return (
this.props.finishItem(index)}>{i.finish ? '撤销完成' : '已完成' }
this.props.deleteItem(index)}>删除
)
})}
)
}
}
ReactDOM.render(
,
document.getElementById('root')
)