React学习笔记

项目地址

写在前面

学习react的时候,我读过的博客,虽然读的不是很细,但是推荐给大家看看。

强烈提醒,去百度react-router的时候要注意版本,因为已经更新到4了,还有很多博客是2和3的

安装

create-react-app

React有很多脚手架,我还是说一下官方推荐的

$ npm install -g create-react-app

这里会生成react的一个项目,后面跟的是名字,这里需要注意,名字不能有大写字母,我平时喜欢用驼峰写法在这里觉得不习惯但是我还是会选择在安装完了后手动改文件夹名字

$ create-react-app my-app

还有一点,这里react生成项目的时候,会自动去安装依赖,而且用的你npm,而不是cnpm。这样会使项目安装的超级慢

nrm

这里推荐一个nrm

github项目地址

这个可以让你的npm命令去走taobao或者cnpm的端口

安装

$ cnpm install -g nrm

使用方法也很简单

查看可用的端口和目前端口

$ nrm ls

使用端口

$ nrm use taobao

改了接口后再去生成项目

生成过后的项目也很简单,和vue-cli生成的项目相比,感觉简洁了很多,最先还以为react那么简洁,后来才发现,只是因为脚手架把webpack的配置隐藏了起来

package.json里面可以看见一条

启用这条命令,就可以把隐藏的东西释放出来

$ npm run eject

启动命令是

$ npm start

关闭Dbug模式,和vue一样,在webpack.config.dev.js找到devtool属性,改为false

虽然react用的jsx的语法,但是现在是可以直接写在js里面,一样的可以用

基本用法

在react里面的概念是,万物皆组件

最基本的方法就是ReactDOM.render(),这个方法传递两个参数,第一个是组件,第二个是获取html页面的DOM元素。将组件添加到获取到的这个DOM中。

这个是官网给出的一个写法,因为第一次写的时候,老是忘记后面那个逗号,弄得很烦。

1
2
3
4
ReactDOM.render(
<h1>Hello, world!</h1>,
document.getElementById('root')
);

直接写一个组件

添加的组件可以是一个方法返回的值,在return前还可以自己做一下操作;

jsx语法里面在遇到{时会解析js语法,在遇到<时会解析html语法。所以标签里面加变量用{ }包裹起来。

组件首写一定要大写

1
2
3
4
5
6
7
8
9
10
11
const Hello = () => {
let text = `world`;
return (
<h1>hello {text}</h1>
)
}
ReactDOM.render(
<Hello />,
document.getElementById('root')
)

用继承的方法去生成一个组件

在生成的项目App.js文件下的开头引用了Component方法,用classextends方法去继承Component,而方法内的一个属性render内的,就是最后输出的标签

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
const Hello = () => {
let text = `world`;
return (
<h1>hello {text}</h1>
)
}
class App extends Component {
render() {
return (
<div className="App">
<Hello />
</div>
);
}
}
//将App暴露出去
export default App;

在入口文件index.js中可以看见importApp.js,然后将<App />输出在了页面

这里需要强调一点,在标签内class需要写成classNamefor需要写成htmlFor,因为他们在js语法里面是保留的关键字。在创建DOM的时候会自动解析成<div class="divClassName"></div>这种形式

组件内获取标签属性

props()

这里我改变了一下文件结构,将Hello组件单独提了出去,写在了src/components/

我的理解是写在App.jsrender()内的<Hello />是标签,src/components/hello.js则是组件,在hello.js内部如何获取到<Hello title="world" />的属性

1
2
3
4
5
export default class Hello extends Component {
render = () => (
<h1>hello,{this.props.title}</h1>
)
}

组件里面render()返回的标签,只能有一个顶级标签,意思就是外面只能有一个最大包裹标签

这个方法应该是可以从父级往子级里面去传值,比如

1
2
3
4
5
6
7
8
9
10
11
12
13
class App extends Component {
state = {
title: 'world'
}
render() {
return (
<div className="App">
<p>{this.state.title}</p>
<Hello title={this.state.title} />
</div>
);
}
}

而且我还可以通过改变值,让他重新加载

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
class App extends Component {
state = {
title: 'world'
}
qwe = () => {
this.setState({
title: this.state.title == 'world' ? 'hello' : 'world'
})
};
render() {
return (
<div className="App">
<p onClick={this.qwe}>click</p>
<p>{this.state.title}</p>
<Hello title={this.state.title} />
</div>
);
}
}

props可以是undefined,string,object

其中有个特别的属性this.props.children,他是组件的所有子节点,可以用React.Children()去做一些操作

App.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
class App extends Component {
state = {
title: 'world'
}
qwe = () => {
this.setState({
title: this.state.title == 'world' ? 'hello' : 'world'
})
};
render() {
return (
<div className="App">
<p onClick={this.qwe}>click</p>
<p>{this.state.title}</p>
<Hello title={this.state.title}>
<span>qwe</span>
<span>zxc</span>
</Hello>
</div>
);
}
}

hello.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
export default class Hello extends Component {
render = () => (
<div>
<h1>hello,{this.props.title}</h1>
<ol>
{
React.Children.map( this.props.children, (child) => (
<li>{child}</li>
))
}
</ol>
</div>
)
}

state()

上面代码中提到了state这个属性,其实可以理解为vue中的data,就是一个组件的初始化的值。在组推荐内部可以用this.state去获取到这些值

其中要重新设置这些值,只能通过方法setState({title: 'hello'})去实现,而不能像vue一样可以直接通过赋值的方法去改变

区分

区分一下stateprops,下面给出阮一峰的博客截图

循环输出

新建一个文件world.jscomponents

声明一个变量,为数组,去循环这个数组,然后return一个<p>标签

其中循环出来的标签需要添加一个key,不然会出现一个警告,原理的话参考vue里面的key

1
2
3
4
5
6
7
8
9
10
11
12
13
14
const arr = [1,2,3,4];
export default class World extends Component {
render() {
return (
<div>
{
arr.map( (name, index)=> {
return <p key={index}>{ name }</p>
})
}
</div>
)
}
}

这个数组里面也可以放标签

1
2
3
4
5
6
7
8
9
10
11
12
13
const arr = [
<p key="1">hello</p>,
<div key="2">world</div>
];
export default class World extends Component {
render() {
return (
<div>
{ arr }
</div>
)
}
}

获取dom

react中都是虚拟的dom,加载到页面中的时候才是实体的dom,虽然不提倡操作dom,但是有时候还是会需要去获取他

给标签添加ref属性

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
export default class World extends Component {
changeName = () => {
const value = this.refs.name.value || `no name`;
alert( value );
}
render() {
return (
<div>
{ arr }
<div>
<input ref="name" placeholder="your name?"/>
<input onClick={this.changeName} type="button" value="say name"/>
</div>
</div>
)
}
}

输入框和数据绑定

vue里面是通过v-model来绑定,react里面是通过onChange方法去改变组件的状态,然后状态更新后也会同步到标签里面

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
export default class World extends Component {
state = {
value: ''
}
changeName = () => {
const value = this.refs.name.value || `no name`;
alert( value);
};
haveName = () => {
const text = this.state.value || 'no name';
return text
};
inputOnChange = (event) => {
this.setState({
value: event.target.value
})
}
render() {
return (
<div>
{ arr }
<div>
<input ref="name" value={this.state.value} onChange={this.inputOnChange} placeholder="your name?"/>
<input onClick={this.changeName} type="button" value="say name"/>
<p>{this.haveName()}</p>
<input type="button" onClick={ ()=>{ this.setState({value: ''}) }} value="clear name"/>
</div>
</div>
)
}
}

这里需要注意,给<input />绑定了value属性,但是没有onChange方法的话,标签会变得无法输入

这里需要提一点,在方法inputOnChange里面获取到一个参数event,这个是获取到了元素的一些属性,其中target就是获取到了元素本身,和vue中的$event是一样的。但是在控制台输出event,再找到target属性却会得到一个null,这也许是因为react里面都是虚拟的DOM的原因,出了组件,虚拟DOM结没办法通过这种方式去获取了。这只是我的想法,毕竟对react的理解还不是特别深,如果有误导,那就对不起咯

选项卡

通过写一个方法,去将标签return回来,就行了

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
class App extends Component {
state = {
title: 'world',
page: true
}
qwe = () => {
this.setState({
title: this.state.title == 'world' ? 'hello' : 'world'
})
};
changePage = () => {
if(this.state.page){
return (
<Hello title={this.state.title}>
<span>qwe</span>
<span>zxc</span>
</Hello>
)
}else{
return <World />
}
}
render() {
return (
<div className="App">
<p onClick={this.qwe}>click</p>
<p>{this.state.title}</p>
<p>
<button onClick={ ()=>{ this.setState({ page: true }) }}>show hello</button>
<button onClick={ ()=>{ this.setState({ page: false }) }}>show world</button>
</p>
{
this.changePage()
}
</div>
);
}
}

其实会发现,react真的如他所说的那样重视js的,专注于js。其实我感觉我爱上了这种写法

生命周期函数

和vue里面的周期函数差不多,就是DOM加载,加载前,加载后,加载中执行的一些方法

生命周期有3个状态:

  • Mounting:已插入真实 DOM
  • Updating:正在被重新渲染
  • Unmounting:已移出真实 DOM

React 为每个状态都提供了两种处理函数,will 函数在进入状态之前调用,did 函数在进入状态之后调用,三种状态共计五种处理函数。

componentWillMount()

componentDidMount()

componentWillUpdate(object nextProps, object nextState)

componentDidUpdate(object prevProps, object prevState)

componentWillUnmount()

还有两个特殊处理函数:

componentWillReceiveProps( Obj nextProps)已加载组件收到新的参数时调用

shouldComponentUpdate( Obj nextProps, ObjnextState)组件判断是否重新渲染时调用

其中componentDidMount可以当做加载创建后会自动执行的方法

补充

不可见状态

比如在进入这个组建时,需要生成一个定时器,离开组件的时候,需要停止这个定时器。

直接写在生命周期函数里面就好。

1
2
3
4
5
6
7
8
9
10
componentDidMount () {
this.timerId = setInterval( () => {
this.setState({
data: new Date()
})
},1000)
}
componentWillUnmount () {
clearInterval( this.timerId )
}

  • 这里的this.timerID其实并没有写到this.state状态里面,因为官方给出说明如下:

    • 虽然 this.props 由React本身设置以及this.state 具有特殊的含义,但如果需要存储不用于视觉输出的东西,则可以手动向类中添加其他字段。

异步状态合并

在组件内部定义组件状态的时候,也许会让this.statethis.props合并成为一个状态内容,但是this.statethis.props有可能是异步更新的,不应该依靠它们的值来计算下一个状态。

不推荐的写法

1
2
3
this.setState({
counter: this.state.counter + this.props.increment,
});

要修复它,请使用第二种形式的 setState() 来接受一个函数而不是一个对象。 该函数将接收先前的状态作为第一个参数,将此次更新被应用时的props做为第二个参数:

推荐的写法

1
2
3
4
5
this.setState( (prevState, props) => (
{
counter: prevState.counter + props.increment
}
));

注意方法里面this的指向

打一个比方,有一个按钮button点击时执行一个sayHi()的方法,然后saiHi()方法里面会去答应当前状态console.log(this.state.hi)

错误的写法

1
2
3
4
5
6
7
8
class App extends Component {
state = {
hi: `Hi~`
}
sayHi () {
console.log(this.state.hi);
}
}

这样会有一个报错

官方推荐了3️三个写法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
//第一种
class App extends Component {
constructor (props) {
super(props);
this.state = {
hi: `Hi~`
}
this.sayHi = this.sayHi.bind(this)
}
sayHi () {
console.log(this.state.hi)
}
}
//第二种
class App extends Component {
constructor (props) {
super(props);
this.state = {
hi: `Hi~`
}
}
sayHi = () => {
console.log(this.state.hi)
}
}
//第三种
class App extends Component {
constructor (props) {
super(props);
this.state = {
hi: `Hi~`
}
}
sayHi () {
console.log(this.state.hi);
}
render () {
return (
<button onClick={ (e) => this.saiHi(e) }>saiHi</button>
)
}
}

合理使用条件渲染

上面说到了用if的方法去做渲染,其实还有几个方法,比如三目,或运算,且运算

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
class App extends Component {
render (
return (
<div>
{this.props.title &&
<div>
{
this.props.title == `1` ? `no` : `yes`
}
</div>
}
</div>
)
)
}
ReactDOM.render(
<App title="1"/>,
document.querySelector('#app')
)

这样可以让代码更简洁,逻辑层没有那么多,可读性变高了

多个输入框

一个组件里面,如果有很多歌输入框,也不用写多个onChange的方法去一一对应,改变状态。可以给input一个name属性,然后去根据name去改变对应的状态

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
class App extends Component {
constructor (props) {
super(props);
this.state = {
input1: ``,
input2: ``
}
}
changeInput = (e) => {
this.setState({
[e.target.name]: e.target.value
})
}
render () {
return (
<ul>
<li>
<input name="input1" value={this.state.input1} onChange={this.changeInput}/>
</li>
<li>
<input name="input2" value={this.state.input2} onChange={this.changeInput}/>
</li>
<li>
<p>{this.state.input1}</p>
<p>{this.state.input2}</p>
</li>
</ul>
)
}
}

这里自己需要注意一下,input标签有没有name或者值是不是对应的value。需要在状态里面先初始化一下

状态提升

既然是模块化,都会涉及到模块和模块之间的数据,应该怎么传递?

和vue一样,在没有vuex的时候会选择用最近的一个父级组件当做一个桥梁。逻辑和vue差不多,父组件定义一个状态,将状态传递到两个组件内。子组件在将好的状态通过一个自定义方法,在父组件里面触发一个方法,然后更新父组件的状态值。父组件的状态值更新后,也会更新传递到子组件的值。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
class App extends Component {
state = {
number: 1
}
fairthChange = (e) => {
this.setState({
inputValue: e.target.value
})
}
upF = (value) => {
console.log(value);
this.setState({
inputValue: value
})
}
downF = (value) => {
this.setState({
inputValue: value
})
}
childrenChangeInput = (value) => {
this.setState({
inputValue: value
})
}
render () {
return (
<div>
<div>
<input name="children" value={this.state.inputValue} onChange={this.fairthChange}/>
</div>
<Children1
title={this.state.inputValue}
onUp={this.upF}
onDown={this.downF}/>
<Children2
title={this.state.inputValue}
onInputChange={this.childrenChangeInput}/>
</div>
)
}
}

Children1

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
export default class Childre1 extends Component {
up = () => {
let number = this.props.title;
number++;
this.props.onUp(number);
}
down = () => {
let number = this.props.title;
number--;
this.props.onDown(number);
}
render = () => (
<div>
<p>{this.props.title}</p>
<div>
<button onClick={this.up}>++</button>
<button onClick={this.down}>--</button>
</div>
</div>
)
}

Children2

1
2
3
4
5
6
7
8
9
10
11
export default class Childre2 extends Component {
childrenInputChange = (e) => {
let value = e.target.value;
this.props.onInputChange(value)
}
render = () => (
<div>
<input value={this.props.title} onChange={this.childrenInputChange}/>
</div>
)
}

我,曼妥思,打钱