React 元素是不可变对象。一旦被创建,你就无法更改它的子元素或者属性。一个元素就像电影的单帧:它代表了某个特定时刻的 UI。
更新 UI 唯一的方式是创建一个全新的元素,并将其传入 ReactDOM.render()
React DOM 会将元素和它的子元素与它们之前的状态进行比较,并只会进行必要的更新来使 DOM 达到预期的状态。
从概念上类似于 JavaScript 函数。它接受任意的入参(即 “props”),并返回用于描述页面展示内容的 React 元素
React 会将以小写字母开头的组件视为原生 DOM 标签。例如,
代表 HTML 的 div 标签,而 则代表一个组件,并且需在作用域内使用 Welcome。
建议从组件自身的角度命名 props,而不是依赖于调用组件的上下文命名。
1,函数组件
function Welcome(props) {
return Hello, {props.name}
;
}
此函数为有效的React组件,因为它接收唯一带有数据的 “props”(代表属性)对象与并返回一个 React 元素
2,ES6 的 class 来定义组件
class Welcome extends React.Component {
render() {
return Hello, {this.props.name}
;
}
}
组件无论是使用函数声明还是通过 class 声明,都决不能修改自身的 props。
所有 React 组件都必须像纯函数一样保护它们的 props 不被更改。
State 与 props 类似,但是 state 是私有的,并且完全受控于当前组件。
1,不要直接修改 State
// 错误
this.state.comment = 'Hello';
// 正确
this.setState({comment: 'Hello'});
2,State 的更新可能是异步的
出于性能考虑,React 可能会把多个 setState() 调用合并成一个调用。
因为 this.props 和 this.state 可能会异步更新,所以你不要依赖他们的值来更新下一个状态。
// 错误
this.setState({
counter: this.state.counter + this.props.increment,
});
// 正确
可以让 setState() 接收一个函数而不是一个对象。
这个函数用上一个 state 作为第一个参数,
将此次更新被应用时的 props 做为第二个参数
this.setState((state, props) => ({
counter: state.counter + props.increment
}));
当你调用 setState() 的时候,React 会把你提供的对象合并到当前的 state。
state合并为浅合并,只替换更新的对象,不会影响到其他对象
state 属于“单向”的数据流。任何的 state 总是所属于特定的组件,而且从该 state 派生的任何数据或 UI 只能影响树中“低于”它们的组件。
如果你把一个以组件构成的树想象成一个 props 的数据瀑布的话,那么每一个组件的 state 就像是在任意一点上给瀑布增加额外的水源,但是它只能向下流动。
class Clock extends React.Component {
constructor(props) {
super(props);
this.state = {date: new Date()};
}
// 方法会在组件已经被渲染到 DOM 中后运行
componentDidMount() {
}
// 方法会在组件被卸载时运行
componentWillUnmount() {
}
render() {
return (
Hello, world!
It is {this.state.date.toLocaleTimeString()}.
);
}
}
// 传统的html
// React
在 React 中另一个不同点是你不能通过返回 false 的方式阻止默认行为。你必须显式的使用 preventDefault
// 传统的html
Click me
// React
function ActionLink() {
function handleClick(e) {
e.preventDefault();
console.log('The link was clicked.');
}
return (
Click me
);
}
----------------------------------------------------
class Toggle extends React.Component {
constructor(props) {
super(props);
this.state = {isToggleOn: true};
// 为了在回调中使用 `this`,这个绑定是必不可少的
this.handleClick = this.handleClick.bind(this);
}
handleClick() {
this.setState(state => ({
isToggleOn: !state.isToggleOn
}));
}
render() {
return (
);
}
}
ReactDOM.render(
,
document.getElementById('root')
);
注:想要在回调中使用this,那么必须要在在构造方法中绑定进行绑定
回调函数中的 this,
在 JavaScript 中,class 的方法默认不会绑定 this。如果你忘记绑定 this.handleClick 并把它传入了 onClick,当你调用这个函数的时候 this 的值为 undefined
也可以使用箭头函数进行this的指向
class LoggingButton extends React.Component {
handleClick() {
console.log('this is:', this);
}
render() {
// 此语法确保 `handleClick` 内的 `this` 已被绑定。
return (
);
}
}
此语法问题在于每次渲染 LoggingButton 时都会创建不同的回调函数。在大多数情况下,这没什么问题,但如果该回调函数作为 prop 传入子组件时,这些组件可能会进行额外的重新渲染。我们通常建议在构造器中绑定或使用 class fields 语法来避免这类性能问题。
function Mailbox(props) {
const unreadMessages = props.unreadMessages;
return (
Hello!
{unreadMessages.length > 0 &&
You have {unreadMessages.length} unread messages.
}
);
}
const messages = ['React', 'Re: React', 'Re:Re: React'];
ReactDOM.render(
,
document.getElementById('root')
);
在 JavaScript 中,true && expression 总是会返回 expression, 而 false && expression 总是会返回 false。
因此,如果条件是 true,&& 右侧的元素就会被渲染,如果是 false,React 会忽略并跳过它。
const todoItems = todos.map((todo, index) =>
// Only do this if items have no stable IDs
{todo.text}
);
使用map进行遍历,可加下标
注:在 map() 方法中的元素需要设置 key 属性。
如果列表项目的顺序可能会变化,不建议使用索引用作key值,这个会导致性能变差,还可能引发状态的问题,可以使用如下方式:
function ListItem(props) {
// 正确!这里不需要指定 key:
return {props.value} ;
}
function NumberList(props) {
const numbers = props.numbers;
const listItems = numbers.map((number) =>
// 正确!key 应该在数组的上下文中被指定
);
return (
{listItems}
);
}
const numbers = [1, 2, 3, 4, 5];
ReactDOM.render(
,
document.getElementById('root')
);
受控组件:表单元素(如、 和 )之类的表单元素通常自己维护 state,并根据用户输入进行更新。而在 React 中,可变状态(mutable state)通常保存在组件的 state 属性中,并且只能通过使用 setState()来更新
受控组件个人理解,就是把state的值绑定到表单元素的value上,当触发事件时,重新进行setState的值,使 React 的 state 成为“唯一数据源”
当需要处理多个 input 元素时,我们可以给每个元素添加 name 属性,并让处理函数根据 event.target.name 的值选择要执行的操作。
class Reservation extends React.Component {
constructor(props) {
super(props);
this.state = {
isGoing: true,
numberOfGuests: 2
};
this.handleInputChange = this.handleInputChange.bind(this);
}
handleInputChange(event) {
const target = event.target;
const value = target.type === 'checkbox' ? target.checked : target.value;
// 此处为表单元素的name
const name = target.name;
// 使用 ES6 计算属性名称的语法更新给定输入名称对应的 state 值
this.setState({
[name]: value
});
}
render() {
return (
);
}
}
如果你想寻找包含验证、追踪访问字段以及处理表单提交的完整解决方案,使用 (Formik 是不错的选择。然而,它也是建立在受控组件和管理 state 的基础之上 —— 所以不要忽视学习它们。
所谓状态提示其实就是把多个组件需要共享的state向上移动到它们的最近公共父组件。
简而言之,就是在父组件当中创建一个方法,子组件数据改变后,通过调用父组件的方法,把值传递到父组件,修改父组件state中的值
// 子组件
class TepmperatureInput extends React.Component {
constructor(props) {
this.handleChange = this.handleChange.bind(this);
}
handleChange(e){
// 父组件定义的方法,把值传递给父组件
this.props.onTemperatureChange(e.target.value);
}
render() {
const temperature = this.props.temperature
return (
)
}
}
// 父组件
class ParentComponent extends React.Component {
constructor(props) {
super(props);
this.state = {inputVal:''}
}
// 子组件回调时方法
onTemperatureChange(temperature) {
this.setState({inputVal:temperature})
}
render() {
const inputVal = this.state.inputVal
return (
)
}
}
tips:建议如果某些数据可以由 props 或 state 推导得出,那么它就不应该存在于 state 中。
function SplitPane(props) {
return (
// 子组件中直接使用props方式调用即可
{props.left}
{props.right}
);
}
// left right 名字可以自己定义,直接当成props传入即可
function App() {
return (
}
right={
} />
);
}
React元素本质就是对象,所以你可以把它们当作 props,像其他数据一样传递。这种方法可能使你想起别的库中“槽”(slot)的概念,但在 React 中没有“槽”这一概念的限制,你可以将任何东西作为 props 进行传递。
确定了组件层级,可以编写对应的应用了。最容易的方式,是先用已有的数据模型渲染一个不包含交互功能的 UI。最好将渲染 UI 和添加交互这两个过程分开。
构建应用方式
注意:
React 中的数据流是单向的,并顺着组件层级从上往下传递。
哪个组件应该拥有某个 state 这件事,对初学者来说往往是最难理解的部分。尽管这可能在一开始不是那么清晰,但你可以尝试通过以下步骤来判断: