Skip to content
On this page

工作已经好几年工作经验了,还没有用过react呢,最近打算把react官网刷一波,写写文章输出一下!

监介

React是一个声明式,高效且灵活的用于构建用户界面的JavaScript库。使用React可以将一些简短、独立的代码片段组合成复杂的UI界面,这些代码片段被称作组件

React并不是一个严格的MVVM框架,而是一个基于组件化的视图库。React是单向数据流:UI = render(data)

JSX模板语法

JSX称为JS的语法拓展,将UI与逻辑层耦合在组件里,用"{}"标识。

JSX 的优点之一是它能够提供更好的可读性和易于理解的代码结构。另一个优点是它允许开发人员更轻松地将 JavaScript 和 HTML 结合在一起,从而更容易创建动态的 UI 组件。JSX 的语法看起来类似于 HTML,但实际上它是一种语法糖,会被转译成 JavaScript 代码。

因为JSX语法上更接近JS而不是HTML,所以使用camelCase(小驼峰命名)来定义属性的名称;JSX里面的class变成了className,而tabindex变成了tabIndex

JSX支持表达式

js
// 变量
const name = 'Josh Perez';
const element = <h1>Hello, {name}</h1>;

function formatName(user) {
  return user.firstName + ' ' + user.lastName;
}

// 方法
const user = {
  firstName: 'Harper',
  lastName: 'Perez'
};

const element = (
  <h1>
    Hello, {formatName(user)}!
  </h1>
);

function getGreeting(user) {
  if (user) {
    return <h1>Hello, {formatName(user)}!</h1>;
  }
  return <h1>Hello, Stranger.</h1>;
}

JSX指定属性

js
const element = <img src={user.avatarUrl}></img>;

JSX表示对象

js
const element = (
  <h1 className="greeting">
    Hello, world!
  </h1>
);

// 等同于React.createElement
const element = React.createElement(
  'h1',
  {className: 'greeting'},
  'Hello, world!'
);

const element = {
  type: 'h1',
  props: {
    className: 'greeting',
    children: 'Hello, world!'
  }
};

将JSX渲染为DOM

js
// 使用ReactDOM.render
const element = <h1>Hello, world</h1>;
ReactDOM.render(element, document.getElementById('root'));

// render只能代表当前时刻的状态
// 更新元素 只能再次 ReactDOM.render
function tick() {
  const element = (
    <div>
      <h1>Hello, world!</h1>
      <h2>It is {new Date().toLocaleTimeString()}.</h2>
    </div>
  );
  ReactDOM.render(element, document.getElementById('root')); 
}

setInterval(tick, 1000); // 不建议多次render

props & state

组件,从概念上类似于 JavaScript 函数。它接受任意的入参(即 “props”),并返回用于描述页面展示内容的 React 元素。

组件

函数式组件

js
function Welcome(props) {
  return <h1>Hello, {props.name}</h1>;
}

类组件

js
class Welcome extends React.Component {
  render() {
    return <h1>Hello, {this.props.name}</h1>;
  }
}

渲染组件

js
function Welcome(props) {
    return <h1>Hello, {props.name}</h1>;
}

const element = <Welcome name="Sara" />;
ReactDOM.render(
    element,
    document.getElementById('root')
);

自定义组件使用大写字母开头

js
function Hello(props) {
  // 正确! 这种 <div> 的使用是合法的,因为 div 是一个有效的 HTML 标签:
  return <div>Hello {props.toWhat}</div>;
}

function HelloWorld() {
  // 正确!React 知道 <Hello /> 是一个组件,因为它是大写字母开头的:
  return <Hello toWhat="World" />;
}

组件的组合和拆分

有了组件可以在父级组件内多次引用

js
<div>
  <Welcome name="Sara" />
  <Welcome name="Cahal" />
  <Welcome name="Edite" />
</div>

组件的拆分

js
function Comment(props) {
  return (
    <div className="Comment">
      <div className="UserInfo">
        <img className="Avatar"
          src={props.author.avatarUrl}
          alt={props.author.name}
        />
        <div className="UserInfo-name">
          {props.author.name}
        </div>
      </div>
      <div className="Comment-text">
        {props.text}
      </div>
      <div className="Comment-date">
        {formatDate(props.date)}
      </div>
    </div>
  );
}

拆分后

js
function UserInfo(props) {
	return (
		<div className="UserInfo">
			<img className="Avatar"
				src={props.avatarUrl}
				alt={props.name}
			/>
			<div className="UserInfo-name">
				{props.name}
			</div>
		</div>
	)
}
function Comment(props) {
	return (
		<div className="Comment">
			<UserInfo user={props.author} />
			<div className="Comment-text">
				{props.text}
			</div>
			<div className="Comment-date">
				{formatDate(props.date)}
			</div>
		</div>
	);
}

受控组件 与 非受控组件

受控组件:对某个组件状态的掌控,它的值是否只能由用户设置,而不能通过代码控制;

在HTML的表单元素中,它们通常自己维护一套state,并随着用户的输入自己进行UI上的更新,这种行为是不被我们程序所管控的。而如果将React里的state属性和表单元素的值建立依赖关系,再通过onChange事件与setState()结合更新state属性,就能达到控制用户输入过程中表单发生的操作。被React以这种方式控制取值的表单输入元素就叫做受控组件。

js
// input自身维护的状态,外界无法获取数据
class TestComponent extends React.Component {
  render () {
    return <input name="username" />
  }
}

// 可以设置初始值
class TestComponent extends React.Component {
  constructor (props) {
    super(props);
    this.state = { username: 'test' };
  }
  render () {
    return <input name="username" value={this.state.username} />
  }
}

// 可以读取并设置初始值
class TestComponent extends React.Component {
  constructor (props) {
    super(props);
    this.state = {
      username: "test"
    }
  }
  onChange (e) {
    console.log(e.target.value);
    this.setState({
      username: e.target.value
    })
  }
  render () {
    return <input name="username" value={this.state.username} onChange={(e) => this.onChange(e)} />
  }

非受控组件:对应的,组件内的状态不由用户控制

js
// 如果不想关心表单元素的值是如何变化的,只想取值,可以使用ref
import React, { Component } from 'react';

export class UnControll extends Component {
  constructor (props) {
    super(props);
    this.inputRef = React.createRef();
  }
  handleSubmit = (e) => {
    console.log('我们可以获得input内的值为', this.inputRef.current.value);
    e.preventDefault();
  }
  render () {
    return (
      <form onSubmit={e => this.handleSubmit(e)}>
        <input defaultValue="lindaidai" ref={this.inputRef} />
        <input type="submit" value="提交" />
      </form>
    )
  }
}

props

React 组件使用 props 来互相通信。每个父组件都可以提供 props 给它的子组件,从而将一些信息传递给它。Props 可能会让你想起 HTML 属性,但你可以通过它们传递任何 JavaScript 值,包括对象、数组和函数。 因为react是单向数据流,所以所有React 组件都必须像纯函数一样保护它们的 props 不被更改。

js
// 错误,要像纯函数一样幂等
function withdraw(account, amount) {
  account.total -= amount;
}

state

如何避免多次render?看下下面的例子

js
// 使用props形式
function Clock(props) {
	return (
		<div>
			<h1>Hello, world!</h1>
			<h2>It is {props.date.toLocaleTimeString()}.</h2>
		</div>
	);
}

function tick() {
	ReactDOM.render(
		<Clock date={new Date()} />,
		document.getElementById('root')
	);
}

setInterval(tick, 1000);

这样就会频繁的render,我们使用生命周期和类组件来解决

js
// 引用生命周期,根组件保留一个
class Clock extends React.Component {
	constructor(props) {
		super(props);
		this.state = { date: new Date() };
	}

	componentDidMount() {
		this.timerID = setInterval(
			() => this.tick(),
			1000
		);
	}

	componentWillUnmount() {
		clearInterval(this.timerID);
	}

	tick() {
		this.setState({
			date: new Date()
		});
	}

	render() {
		return (
			<div>
				<h1>Hello, world!</h1>
				<h2>It is {this.state.date.toLocaleTimeString()}.</h2>
			</div>
		);
	}
}

ReactDOM.render(
	<Clock />,
	document.getElementById('root')
);

setState

js
this.setState({comment:'hihi'})
state更新可能是异步的
js
// Wrong
this.setState({
  counter: this.state.counter + this.props.increment,
});
// Correct
this.setState(function(state, props) {
  return {
    counter: state.counter + props.increment
  };
});
state更新会合并
js
class Clock extends React.Component {
	constructor(props) {
		super(props);
		this.state = {
			posts: [],
			comments: []
		};
	}

	componentDidMount() {
		fetchPosts().then(response => {
			// 相当于{post: response.posts, ...otherState}
			this.setState({
				posts: response.posts
			});
		});

		fetchComments().then(response => {
			this.setState({
				comments: response.comments
			});
		});
	}

}
单向数据流

state 只在当前的组件里生效,属于组件内的属性,重复实例化相同的组件,内部的内存地址也是不一样的;

合成事件
js
class App extends Component {

	state = { val: 0 }

	increment = () => {
		this.setState({ val: this.state.val + 1 })
		console.log(this.state.val) // 输出的是更新前的val --> 0
	}

	render() {
		return (
			<div onClick={this.increment}>
				{`Counter is: ${this.state.val}`}
			</div>
		)
	}
}

setState是异步的所以在打印的时候输出的是更新前的值。

在生命周期中
js
class App extends Component {
	
	state = { val: 0 }
	
	changeValue = () => {
		this.setState({ val: this.state.val + 1 })
		console.log(this.state.val) // 输出的是更新后的值 --> 1
	}
	
	componentDidMount() {
		document.body.addEventListener('click', this.changeValue, false)
	}
	
	render() {
		return (
			<div>
				{`Counter is: ${this.state.val}`}
			</div>
		)
	}
}

在生命周期中更新,打印的时候输出的是更新前的值。

原生事件
js
class App extends Component {
	
	state = { val: 0 }
	
	changeValue = () => {
		this.setState({ val: this.state.val + 1 })
		console.log(this.state.val) // 输出的是更新后的值 --> 1
	}
	
	componentDidMount() {
		document.body.addEventListener('click', this.changeValue, false)
	}
	
	render() {
		return (
			<div>
				{`Counter is: ${this.state.val}`}
			</div>
		)
	}
}

setTimeout
js
class App extends Component {
	
	state = { val: 0 }
	
	componentDidMount() {
		setTimeout(_ => {
			this.setState({ val: this.state.val + 1 })
			console.log(this.state.val) // 输出更新后的值 --> 1
		}, 0)
	}
	
	render() {
		return (
			<div>
				{`Counter is: ${this.state.val}`}
			</div>
		)
	}
}

批处理
js
class App extends Component {
	
	state = { val: 0 }
	
	batchUpdates = () => {
		this.setState({ val: this.state.val + 1 })
		this.setState({ val: this.state.val + 1 })
		this.setState({ val: this.state.val + 1 })
	}
	
	render() {
		return (
			<div onClick={this.batchUpdates}>
				{`Counter is ${this.state.val}`} // 1
			</div>
		)
	}
}
  • setState 只在合成事件和生命周期中是“异步”的,在原生事件和 setTimeout 中都是同步的;
  • setState的“异步”并不是说内部由异步代码实现,其实本身执行的过程和代码都是同步的, 只是合成事件和钩子函数的调用顺序在更新之前,导致在合成事件和钩子函数中没法立马拿到更新后的值,形式了所谓的“异步”, 当然可以通过第二个参数 setState(partialState, callback) 中的callback拿到更新后的结果。
  • setState 的批量更新优化也是建立在“异步”(合成事件、钩子函数)之上的,在原生事件和setTimeout 中不会批量更新,在“异步”中如果对同一个值进行多次 setState , setState 的批量更新策略会对其进行覆盖,取最后一次的执行,如果是同时 setState 多个不同的值,在更新时会对其进行合并批量更新。

条件渲染

通常你的组件会需要根据不同的情况显示不同的内容。在 React 中,你可以通过使用 JavaScript 的 if 语句、&&? : 运算符来选择性地渲染 JSX。

if else渲染

js
class LoginControl extends React.Component {
  constructor(props) {
    super(props);
    this.handleLoginClick = this.handleLoginClick.bind(this);
    this.handleLogoutClick = this.handleLogoutClick.bind(this);
    this.state = {isLoggedIn: false};
  }

  handleLoginClick() {
    this.setState({isLoggedIn: true});
  }

  handleLogoutClick() {
    this.setState({isLoggedIn: false});
  }

  render() {
    const isLoggedIn = this.state.isLoggedIn;
    let button;
    if (isLoggedIn) {
      button = <LogoutButton onClick={this.handleLogoutClick} />;
    } else {
      button = <LoginButton onClick={this.handleLoginClick} />;
    }

    return (
      <div>
        <Greeting isLoggedIn={isLoggedIn} />
        {button}
      </div>
    );
  }
}

ReactDOM.render(
  <LoginControl />,
  document.getElementById('root')
);

与运算符 &&

js
function Mailbox(props) {
  const unreadMessages = props.unreadMessages;
  return (
    <div>
      <h1>Hello!</h1>
      {unreadMessages.length > 0 &&
        <h2>
          You have {unreadMessages.length} unread messages.
        </h2>
      }
    </div>
  );
}

const messages = ['React', 'Re: React', 'Re:Re: React'];
ReactDOM.render(
  <Mailbox unreadMessages={messages} />,
  document.getElementById('root')
);

// 返回false的表达式,会跳过元素,但会返回该表达式
render() {
  const count = 0;
  return (
    <div>
      { count && <h1>Messages: {count}</h1>}
    </div>
  );
}

三元运算符

js
render() {
  const isLoggedIn = this.state.isLoggedIn;
  return (
    <div>
      {isLoggedIn
        ? <LogoutButton onClick={this.handleLogoutClick} />
        : <LoginButton onClick={this.handleLoginClick} />
      }
    </div>
  );
}

列表渲染

你将依赖 JavaScript 的特性,例如 for 循环 和 array 的 map() 函数来渲染组件列表。

js
function NumberList(props) {
  const numbers = props.numbers;
  const listItems = numbers.map((number) =>
    <li key={number.toString()}>
      {number}
    </li>
  );
  
  return (
    <ul>{listItems}</ul>
  );
}

const numbers = [1, 2, 3, 4, 5];
ReactDOM.render(
  <NumberList numbers={numbers} />,
  document.getElementById('root')
);
// 若没有key,会warning a key should be provided for list items
// key可以帮助react diff,最好不用index作为key,会导致性能变差;
// 如果不指定显式的 key 值,默认使用索引用作为列表项目的 key 值;

总结

今天先肝这些,把一些比较基础简单的东西写了一下,基本上可以写一些简单的demo,估计能上手开发了。奈何没有练手的项目,只能自己捣鼓了。

参考