欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

React精髓!一篇全概括小结(急速)

程序员文章站 2023-12-17 12:49:58
学和使用react有一年多了,最近想在梳理一下react基础知识,夯实基础,激流勇进~ 关于reacr-router,redux,redux-saga后续都会慢慢输出,希...

学和使用react有一年多了,最近想在梳理一下react基础知识,夯实基础,激流勇进~
关于reacr-router,redux,redux-saga后续都会慢慢输出,希望各位看官老爷持续关注~~要是能给个赞鼓励一下就更赞了~

提醒一下:

看完之后抓紧时间趁热打铁,redux,react-redux,redux-saga

react基础知识速览

1、什么是jsx?

一个jsx语法的示例,如下所示

const element = <h1>hello, world!</h1>;

这种语法形式,既不是html,也不是字符串,而是称之为jsx,是react里用来描述ui和样式的语法,jsx最终会被编译为合法的js语句调用(编译器在遇到{时采用js语法进行解析,遇到<就采用html规则进行解析)

2、嵌入表达式

jsx中,可以使用花括号{}嵌入任意的javascript合法表达式,如:2 + 2、user.firstname、formatname(user)都是合法的。示例如:

const user = {
 firstname: 'zhang',
 lastname : 'busong'
};

const elem = (
 <h1>hello, {formatname(user)}</h1>
);
/*
这里的(),实际上是可选的,但是react推荐加入(),这样子就会被视为一个表达式,而不会导致
自动插入分号的问题
*/

reactdom.render(
 element,
 document.getelementbyid('app')
)

3、jsx也是一种表达式

jsx本身也是一种表达式,所以它可以像其他表达式一样,用于给一个变量赋值、作为函数实参、作为函数返回值,等等。如:

function getgreeting(user) {
 if (user) {
  return <h1>hello, {formatname(user)}</h1>
 }
 return <h1>hello, guest!</h1>;
}

注意:

1、在jsx中,声明属性时不要使用引号,如果声明属性的时候使用引号,那么将被作为字符串解析,而不会被作为一个表达式解析,如:

<div firstname="{user.firstname}" lastname={user.lastname}></div>

解析后,可以得到:

<div firstname="{user.firstname}" lastname="lau"></div>

因此,当我们需要使用一个字符串字面量的时候,可以使用引号,但是如果要作为表达式解析的时候,则不应当使用引号

2、在jsx中,有些属性名称需要进行特殊处理。如class应该用classname代替,tabindex则用tabindex代替。这是因为jsx本质上更接近于javascript,而class是javascript中的保留字。同时,应该使用camelcase来命名一个属性,而不是使用html的属性命名方式

3、jsx本身已经做了防注入处理,对于那些不是明确编写的html代码,是不会被解析为html dom的,reactdom会将他们一律视为字符串,在渲染完成前就转化为字符串,所以可以防止xss攻击

4、如果jsx标签是闭合的,那么结尾需要用/>,另外,jsx标签是可以互相嵌套的,这和html里是一样的

4、jsx实质

jsx通过babel编译,而babel实际上把jsx编译给react.createelement()调用。如下jsx代码:

const element = (
 <h1 classname="greeting">
  hello, world!
 </h1>
);

是等同于以下的语句的:

const elem = react.createelement(
 'h1',
 {classname: 'greeting'},
 'hello, world!'
);

react.createelement()方法会首先进行一些避免bug的检查,然后返回类似以下例子的对象:

const element = {
 type: 'h1',
 props: {
  classname: 'greeting',
  children: 'hello, world'
 }
}

这样的对象,则称为react元素,代表所有呈现在屏幕上的东西。react正是通过读取这些对象来构建dom,并且保持数据和ui同步的

5、元素渲染

元素(elements)是构成react应用的最小单元,元素描述了想要在屏幕中看到的内容,如:

const element = <h1>hello, world</h1>;

和dom元素不同的是,react元素是纯对象,创建的代价低。并且react会进行优化处理,只把有必要的变化更新到dom上。此外,元素和组件的概念,是不一样的,组件是由元素组成的。

6、将元素渲染进dom

在react中,使用reactdom.render()方法来将react元素渲染进一个dom中。如:

reactdom.render(
 element,
 document.getelementbyid('root')
)

react元素是不可变的,所以一旦一个元素创建完成后,我们是无法改变其内容或者属性的。一个元素就像是动画里的一帧,它代表ui在某一时间点的样子。如果非要使用元素来构成可变化的ui界面,就需要使用setinterval了,如:

function tick() {
 const element = (
  <div>now is {new date().tolocaletimestring()}</div>
 );
 reactdom.render(
  element,
  document.getelementbyid('root')
 );
}
setinterval(tick, 1000);

在实际开发中,大多数react应用只会调用一次reactdom.render(),所以更好的方式是使用有状态组件

7、组件和props

组件(component)能够将ui划分为独立的、可复用的部分,这样我们就只需专注于构建每一个单独的部件。
从概念上看,组件就像是函数:接受任意的输入(称为属性,props),返回react元素。react中有两种定义组件的方式:函数定义和类定义

1、函数定义组件

这种方式是最简单的定义组件的方式,就像写一个js函数一样,如:

function welcome (props) {
 return <h1>hello, {props.name}</h1>;;
}

2、类定义组件

还可以使用es6里的类来定义一个组件,如下所示:

class welcome extends react.component {
 render () {
  return <h1>hello, {this.props.name}<h1>;
 }
}

这种方式比起函数定义方式则更加灵活

3、组件渲染

先前,我们遇到的react元素只是呈现一个dom标签,如:

const element = <div />

然而,react元素也可以是用户自定义的组件,如:

const element = <welcome name="tom" />

welcome组件中声明了一个属性name="tom",而这个属性,将以props.name的方式传递给组件,如下方式:

function welcome (props) {
 return <h1>hello, {props.name}</h1>;
}

此时,对于以下的代码:

reactdom.render(
 <welcome name="张不怂" />,
 document.getelementbyid('root')
)

最终就会以<h1>hello, 张不怂</h1>的方式呈现。在这个过程中,发生了如下的事情:

  • 对<welcome name="张不怂" />元素调用了reactdom.render()丰富
  • react将{ name: '张不怂' }作为props实参来调用welcome组件
  • welcome完成渲染,返回<h1>hello, 张不怂</h1>元素
  • reactdom计算最小更新代价,然后更新dom

4、组合组件

组件是可以组合的。即组件内部可以引用其他组件,如:

function welcome (props) {
 return <h1>hello, {props.name}</h1>;
}

function app () {
 return (
  <div>
   <welcome name="tom" />
   <welcome name="jack" />
   <welcome name="mike" />
  </div>
 )
}

reactdom.render(
 <app />,
 document.getelementbyid('root')
)

注意: 在react中,组件必须返回单一的根元素,这也是为什么app组件中需要用<div>标签包裹的原因。如以下的方式,是错误的(因为它有3个根元素):

function app () {
 return (
  <welcome name="tom" />
  <welcome name="jack" />
  <welcome name="mike" />
 )
}

5、属性是只读的

考虑以下这种情况:

function sum (a, b) {
 return a + b;
}

这种函数称为纯函数:它不改变自己的输入值,且总是对相同的输入返回相同的结果。

与之对立的,则是非纯函数,如:

function withdraw (account, amount) {
 account.total -= amount;
}

非纯函数在函数内改变了输入的参数。在react中,无论是通过function还是class声明组件,我们都不应该修改它自身的属性(props)。虽然react相当灵活,但是它也有一个严格的规定:所有的react组件都必须像纯函数那样来使用它们的props

8、state与生命周期

使用类定义组件有一些额外的好处,如拥有本地状态这一特性。

以下是一个类定义组件

class clock extends react.component {
 render () {
  return (
   <div>
    <h1>hello, world!</h1>
    <h2>now is {this.props.date.tolocaletimestring()}</h2>
   </div>
  );
 }
}

需要注意的有:

类名即为组件名(无论是函数定义组件还是类定义组件,组件名称的首字母都必须大写,并且继承自react.component)
使用 render() 方法,用来返回需要呈现的内容

1、在类中加入state

state是属于一个组件自身的。我们可以在类的构造函数constructor中来初始化状态,如:

constructor (props) {
 super(props)
 this.state = {
  date: new date()
 }
}

如此一来,我们就可以在render()函数中使用this.state.xxx来引用一个状态

2、生命周期

在应用里,往往都会有许许多多的组件。在组件销毁后,回收和释放它们所占据的资源非常重要。

在时钟应用的例子里,我们需要在第一次渲染到dom的时候设置一个定时器,并且需要在相应的dom销毁后,清除这个定时器。那么,这种情况下,react为我们提供了生命周期的钩子函数,方便我们进行使用。在react中,生命周期分为:

1)mount 已插入真实dom
2)update 正在重新渲染
3)unmount 已移出真实dom

而相应的,生命周期钩子函数有:

  • componentwillmount
  • componentdidmount
  • componentwillupdate(newprops, nextstate)
  • componentdidupdate(prevprops, prevstate)
  • componentwillunmount()

此外,还有两种特殊状态的处理函数:

  • componentwillreceiveprops(nextprops) 已加载的组件收到新的参数时调动
  • shouldcomponentupdate(nextprops, nextstate) 组件判断是否重新渲染时调用

因此,基于生命周期钩子函数,我们可以实现一个时钟应用如下:

class clock extends react.component {
 constructor (props) {
  super(props);
  this.state = {
   date: new date()
  }
 }
 tick () {
  this.setstate({
   date: new date()
  });
 }
 componentdidmount () {
  this.timerid = setinterval(() => {
   this.tick()
  }, 1000);
 }
 componentwillunmount () {
  clearinterval(this.timerid);
 }
 render () {
  return (
   <div>now is {this.state.date.tolocaletimestring()}</div>
  );
 }
}

需要注意的是:

1)render()里用不到的state,不应该声明在state里
2)不能直接使用this.state.xxx = xxx的方式来改变一个state的值,应该使用this.setstate()。如:

setname () {
 this.setstate({
  name: '张不怂'
 })
}

this.setstate()会自动覆盖this.state里相应的属性,并触发render()重新渲染。

3)状态更新可能是异步的

react可以将多个setstate()调用合并成一个调用来提升性能。且由于this.props和this.state可能是异步更新的,所以不应该依靠它们的值来计算下一个状态。这种情况下,可以给setstate传入一个函数,如:

this.setstate((prevstate, props) => ({
 counter: prevstate.counter + props.increment
}));

9、事件处理

react元素的事件与dom元素类似,不过也有一些区别,如:

1)react事件使用camelcase命名(onclick),而不是全小写的形式(onclick)
2)使用jsx,传入的是事件的句柄,而不是一个字符串

如以下的html:

<button onclick="increment()">add</button>

使用react的方式描述如:

<button onclick={increment}>add</button>

还有一个不同在于,在原生dom中,我们可以通过返回false来阻止默认行为,但是这在react中是行不通的,在react中需要明确使用preventdefault()来阻止默认行为。如:

function actionlink () {
 function handleclick (e) {
  e.preventdefault();
  alert('hello, world!');
 }

 return (
  <a href="#" rel="external nofollow" onclick={handleclick}>click me</a>
 );
}

这里,事件回调函数里的event是经过react特殊处理过的(遵循w3c标准),所以我们可以放心地使用它,而不用担心跨浏览器的兼容性问题。

注意: 在使用事件回调函数的时候,我们需要特别注意this的指向问题,因为在react里,除了构造函数和生命周期钩子函数里会自动绑定this为当前组件外,其他的都不会自动绑定this的指向为当前组件,因此需要我们自己注意好this的绑定问题,
通常而言,在一个类方式声明的组件里使用事件回调,我们需要在组件的constructor里绑定回调方法的this指向,如:

class counter extends react.component {
 constructor (props) {
  super(props);
  this.state = {
   counter: 0
  }
  // 在这里绑定指向
  this.increment = this.increment.bind(this);
 }
 increment () {
  this.setstate({
   counter: this.state.counter + 1
  });
 }
 render () {
  return (
   <div>
    the counter now is: {this.state.counter}
    <button onclick={this.increment}>+1</button>
   </div>
  );
 }
}

当然,我们还有另外一种方法来使用箭头函数绑定指向,就是使用实验性的属性初始化语法,如:

class counter extends react.component {
 increment: () => {
  this.setstate({
   counter: this.state.counter + 1
  });
 }
 // ...
}

3)像事件处理程序传递参数

我们可以为事件处理程序传递额外的参数,方式有以下两种:

<button onclick={(e) => this.deleterow(id, e)}>delete row</button>
<button onclick={this.deleterow.bind(this, id)}>delete row</button>

需要注意的是,使用箭头函数的情况下,参数e要显式传递,而使用bind的情况下,则无需显式传递(参数e会作为最后一个参数传递给事件处理程序)

10、条件渲染

在react里,我们可以创建不同的组件来封装我们需要的功能。我们也可以根据组件的状态,只渲染组件中的一部分内容,而条件渲染就是为此而准备的。在react中,我们可以像在javascript中写条件语句一样地写条件渲染语句,如:

function greet(props) {
 const islogined = props.islogined;
 if (islogined) {
  return <div>hello !</div>;
 }
 return <div>please sign in</div>;
}

reactdom.render(
 <greet islogined={true} />,
 document.getelementbyid('root')
);

这将渲染出:

<div>hello !</div>

1、使用变量来存储元素

我们也可以使用变量来存储元素,如:

function logbtn(props) {
 var button;
 const islogined = props.islogined;
 if (islogined) {
  button = <button>退出</button>
 } else {
  button = <button>登陆</button>
 }
 return <div>you can {button}</div>;
}

reactdom.render(
 <logbtn islogined={false} />,
 document.getelementbyid('root')
);

2、使用&&运算符进行渲染

由于javascript语法对待&&运算符的性质,我们也可以使用&&运算符来完成条件渲染,如:

function logbtn(props) {
 var button;
 const islogined = props.islogined;
 return (
  <div>hello
  {!islogined && (
   <button>请登陆</button>
  )}
  </div>
 )
}

当props.islogined为false的时候,就会渲染出:

<div>hello <button>请登录</button></div>

3、使用三目运算符进行渲染

我们可能已经发现了,其实jsx可以像一个表达式那样子灵活使用,所以,我们自然也可以使用三目运算符进行渲染,如:

function logbtn (props) {
 const islogined = props.islogined;
 return (
  <div>you can 
   <button>{islogined ? '退出' : '登陆'}</button>
  </div>
 )
}

4、阻止整个组件的渲染

有时候,我们希望是整个组件都不渲染,而不仅仅是局部不渲染,那么这种情况下,我们就可以在render()函数里返回一个null,来实现我们想要的效果,如:

function logbtn (props) {
 const islogined = props.islogined;
 const isshow = props.isshow;
 if (isshow) {
  return (
   <div>you can 
    <button>{islogined ? '退出' : '登陆'}</button>
   </div>
  )
 }
 return null;
}

注意: 组件里返回null不会影响组件生命周期的触发,如componentwillupdate和componentdidupdate仍然会被调用

11、列表渲染与keys

在javascript中,我们可以使用map()函数来对一个数组列表进行操作,如:

const numbers = [1, 2, 3, 4, 5];
const doubled = numbers.map(number => number*2);
console.log(doubled); // 得到[2, 4, 6, 8, 10]

同样的,在react里,我们也可以使用map()来进行列表渲染,如:

const numbers = [1, 2, 3, 4, 5];
const listitems = numbers.map(number => {
 return (
  <li>{number}</li>
 )
});

reactdom.render(
 <ul>{listitems}</ul>,
 document.getelementbyid('root')
)

这将得到:

<ul><li>1</li>
 <li>2</li>
 <li>3</li>
 <li>4</li>
 <li>5</li>
</ul>

当然,我们还可以进行更好的封装,如:

function numberlist (props) {
 const numbers = props.numbers;
 const listitems = numbers.map(number => {
  return (
   <li>{number}</li>
  )
 });

 return <ul>{listitems}</ul>
}

当我们运行以上的代码的时候,会发现控制台提示:each child in an array or iterator should have a unique "key" prop,因此,我们需要为列表项的每一个项分配一个key,来解决这个问题,通常而言,我们可以使用以下几种方式来提供key:

使用数据项自身的id,如<li key={item.itemid}>
使用索引下标(index),如:

const listitems = numbers.map((number, index) => {
 <li key={index}>{number}</li>
});

但是react不推荐在需要重新排序的列表里使用索引下标,因为会导致变得很慢。
注意: 只有在一个项的同胞里区分彼此的时候,才需要使用到key,key不需要全局唯一,只需要在一个数组内部区分彼此时唯一便可。key的作用是给react一个提示,而不会传递给组件。如果我们在组件内需要同样的一个值,可以换个名字传递,如:

const content = posts.map(post => (
 <post key={post.id} id={post.id} title={post.title} />
));

12、表单

表单和其他的react中的dom元素有所不同,因为表单元素生来就是为了保存一些内部状态。在react中,表单和html中的表单略有不同

1、受控组件

html中,<input>、<textarea>、<select>这类表单元素会维持自身状态,并根据用户输入进行更新。不过react中,可变的状态通常保存在组件的this.state中,且只能用setstate()方法进行更新,如:

class nameform extends react.component {
 constructor (props) {
  super(props);
  this.state = {
   value: ''
  }
  this.handlechange = this.handlechange.bind(this);
  this.handlesubmit = this.handlesubmit.bind(this);
 }
 handlechange (event) {
  this.setstate({
   value: event.target.value
  });
 }
 handlesubmit (event) {
  alert('your name is '+this.state.value);
  event.preventdefault();
 } 
 render () {
  return (
   <form onsubmit={this.handlesubmit}>
   name: <input type="text" value={this.state.value} onchange={this.handlechange} />
   <input type="submit" value="submit" />
   </form>
  )
 }
}

和html中不同的是,react中的textarea并不需要写成<textarea></textarea>的形式,而是写成<textarea value="" ... />的形式便可。而对于html中的select标签,通常做法是:

<select>
 <option value="a">a</option>
 <option value="b" selected>b</option>
 <option value="c">c</option>
</select>

但是react中,不需要在需要选中的option处加入selected,而只需要传入一个value,就会自动根据value来选中相应的选项,如:

<select value="c">
 <option value="a">a</option>
 <option value="b">b</option>
 <option value="c">c</option>
</select>

那么如上述例子,c所在的这个option就会被选中

2、多个输入的解决办法

通常一个表单都有多个输入,如果我们为每一个输入添加处理事件,那么将会非常繁琐。好的一个解决办法是,使用name,然后根据event.target.name来选择做什么。如:

class form extends react.component {
 constructor (props) {
  super(props);
  this.state = {
   name: '',
   gender: '男',
   attend: false,
   profile: ''
  };
  this.handleinputchange = this.handleinputchange.bind(this);
  this.handlesubmit = this.handlesubmit.bind(this);
 }
 handleinputchange (event) {
  const target = event.target;
  const value = target.type==='checkbox' ? target.checked : target.value;
  const name = target.name;
  this.setstate({
   [name]: value
  });
 }
 handlesubmit (event) {
  this.setstate({
   profile: `姓名:${this.state.name},${this.state.gender},${this.state.attend ? '参加' : '不参加'}活动`
  });
  event.preventdefault();
 } 
 render () {
  return (
   <form>
   <p>姓名:<input name="name" value={this.state.name} onchange={this.handleinputchange} /></p>
   <p>性别:
    <select name="gender" value={this.state.gender} onchange={this.handleinputchange}>
     <option value="男">男</option>
     <option value="女">女</option>
    </select>
   </p>
   <p>是否参加:<input name="attend" type="checkbox" onchange={this.handleinputchange} checked={this.state.attend} /></p>
   <input type="submit" value="submit" onclick={this.handlesubmit} />
   <p>您的报名信息:{this.state.profile}</p>
   </form>
  )
 }
}

3、非受控组件

大多数情况下,使用受控组件实现表单是首选,在受控组件中,表单数据是交由react组件处理的。如果想要让表单数据由dom处理(即数据不保存在react的状态里,而是保存在dom中),那么可以使用非受控组件,使用非受控组件,可以无需为每个状态更新编写事件处理程序,使用ref即可实现,如:

class nameform extends react.component {
 constrcutor(props) {
  super(props)
 }
 handlesubmit(event) {
  console.log('a name was submitted: ', this.input.value)
  event.preventdefault()
 }
 render() {
  return (
   <form onsubmit={this.handlesubmit}>
    <label>
    name: <input type="text" ref={input => this.input = input} />
    </label>
    <input type="submit" value="submit" />
   </form>
  )
 }
}

对于非受控组件,如果要指定默认值,那么可以使用defaultvalue,如:

<input type="text" defaultvalue="hello" ref={input => this.input = input} />

相应的,type="checkbox"和type="radio",则使用defaultchecked

13、状态提升

当需要几个组件共用状态数据的时候,可以使用状态提升技术。核心思想在于:把数据抽离到最近的共同父组件,父组件管理状态(state),然后通过属性(props)传递给子组件。如实现一个货币转换的组件,可以如下:

1、首先定义转换函数

function usd2rmb (amount) {
 return amount * 6.7925;
}

function rmb2usd (amount) {
 return amount * 0.1472;
}

function convert (amount, typefn) {
 return typefn(amount);
}

2、定义组件

我们希望在rmb的输入表单上上输入的时候,usd的输入表单上的数值也同步更新,这种情况下,如果rmb组件自己管理自己的状态,是很难以实现的,因此,我们需要让这个状态提升自父组件进行管理。如下:

class currencyinput extends react.component {
 constructor (props) {
  super(props)
  this.handlechange = this.handlechange.bind(this)
 }
 handlechange (event) {
  this.props.oninputchange(event.target.value)
 }
 render () {
  const value = this.props.value
  const type = this.props.type
  return (
   <p>{type}: <input type="text" value={value} onchange={this.handlechange} /></p>
  );
 }
}

最后定义一个共同的父组件,如下:

class currencyconvert extends component {
 constructor (props) {
  super(props);
  this.state = {
   type: 'rmb',
   amount: 0
  }
  this.handlermbchange = this.handlermbchange.bind(this);
  this.handleusdchange = this.handleusdchange.bind(this);
 }
 handlermbchange (amount) {
  this.setstate({
   type: 'rmb',
   amount
  });
 }
 handleusdchange (amount) {
  this.setstate({
   type: 'usd',
   amount
  });
 }
 render () {
  const type = this.state.type;
  const amount = this.state.amount;
  const rmb = type==='rmb' ? amount : convert(amount, usb2rmb);
  const usd = type==='usd' ? amount : convert(amount, rmb2usb);
  return (
   <div>
    <p>please input:</p>
    <currencyinput type="rmb" value={rmb} oninputchange={this.handlermbchange} />
    <currencyinput type="usd" value={usd} oninputchange={this.handleusdchange} />
   </div>
  );
 }
}

14、组合vs继承

react推崇更多的是使用组合,而非使用继承。对于一些使用场景,react给出的建议如下:

1、包含关系

当父组件不知道子组件可能的内容是什么的时候,可以使用props.children,如:

function article (props) {
 return (
  <section>
   <aside>侧边栏</aside>
   <article>{props.children}</article>
  </section>
 );
}

function app () {
 return (
  <article>这是一篇文章</article>
 );
}

这将渲染得到:

<section>
 <aside>侧边栏</aside>
 <article>这是一篇文章</article>
</section>

我们还可以自定义名称,因为jsx实际上会被转化为合法的js表达式,所以,还可以有:

function article (props) {
 return (
  <section>
   <aside>{props.aside}</aside>
   <article>{props.children}</article>
  </section>
 );
}

function app () {
 return (
  <article aside={
   <h1>这是一个侧栏</h1>
  }>这是一篇文章</article>
 );
}

这将渲染得到:

<section>
 <aside><h1>这是一个侧栏</h1></aside>
 <article>这是一篇文章</article>
</section>

2、何时使用继承?

在facebook的网站上,使用了数以千计的组件,但是实践证明还没有发现需要使用继承才能解决的情况。

属性和组合为我们提供了清晰的、安全的方式来自定义组件的样式和行为,组件可以接受任意元素,包括:基本数据类型、react元素、函数。

如果要在组件之间复用ui无关的功能,那么应该将其提取到单独的javascript模块中,这样子可以在不对组件进行扩展的前提下导入并使用函数、对象、类

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持。

上一篇:

下一篇: