React从入门到精通教程
React从入门到精通教程
往期回顾:
组件和Props
-
概述
组件是React中一个非常重要的概念,React将UI页面拆分为一个个的独立单元,每一个独立单元都是独立的,这个独立单元叫做组件。使用组件我们可以很方便的复用一些重复代码,为我们的工作带来了极大的便利。
-
函数组件与class组件
定义组件有两种方式,使用函数或者ES6 里面的class
// 函数组件,接受一个参数`prop`并返回一个React元素 function Welcome(props) { return <h1>Hello, {props.name}</h1>; } // class组件 class Welcome extends React.Component { render() { return <h1>Hello, {this.props.name}</h1>; } }
后面会详细的讲解这两种定义方式的好处和坏处以及应用场景
-
渲染我们的自定义组件
我们可以渲染原先就存在的HTML标签元素,也可以渲染我们自定义的组件。当 React 元素为用户自定义组件时,它会将 JSX 所接收的属性(attributes)以及子组件(children)转换为单个对象传递给组件,这个对象被称之为 “props”。
例如,这段代码会在页面上渲染 “Hello, Sara”:
function Welcome(props) { return <h1>Hello, {props.name}</h1>; } const element = <Welcome name="Sara" />; ReactDOM.render( element, document.getElementById('root') ); // 自定义组件一般以大写字母开头,上面我们定义了一个Welcome组件,这个组件传入了一个对象props,对象里面有一个属性name
我们来回顾一下这个例子中发生了什么:
- 我们调用
ReactDOM.render()
函数,并传入<Welcome name="Sara" />
作为参数。 - React 调用
Welcome
组件,并将{name: 'Sara'}
作为 props 传入。 Welcome
组件将<h1>Hello, Sara</h1>
元素作为返回值。- React DOM 将 DOM 高效地更新为
<h1>Hello, Sara</h1>
。
- 我们调用
-
组件中嵌套组件
- 组件可以在其输出中引用其他组件。这就可以让我们用同一组件来抽象出任意层次的细节。按钮,表单,对话框,甚至整个屏幕的内容:在 React 应用程序中,这些通常都会以组件的形式表示。组件中嵌套组件的方式为我们写代码提供了更高的复用性和灵活性
function UserIfo(porps) { return ( <div> <img src={porps.user.avatar} /> <p>name: {porps.user.name}</p> </div> ) } function Description(props){ return <h6>{props.description}</h6> } const peopleIfo = { user: { avatar: 'https://placekitten.com/g/64/64', name: 'kobe', }, description: 'my name is kobe', } function Comment(props) { console.log(props) return ( <div> <h3>{`我是一个复合组件,传进来的值为${JSON.stringify(props)}`}</h3> <UserIfo user={props.peopleIfo.user} /> <Description description={props.peopleIfo.description} /> </div> ) } ReactDOM.render(<Comment peopleIfo={peopleIfo}/>, document.getElementById('root'))
结果如下:
-
提取组件
我们一个组件最好是不要设计的特别大,不然复用性比较低,如果我们设计的组件比较大,我们就要适当的进行组件拆分,将一个大组件拆分为很多个小组件
-
props的只读性
-
纯函数:不会更改入参的函数,多次调用下相同的入参始终返回相同的结果
-
非纯函数:会更改自己的入参
function sum(a, b) { return a + b; } // 纯函数 function withdraw(account, amount) { account.total -= amount; } // 非纯函数
React组件也可以看作是一个函数,它接受一个
props
作为参数,并返回一个要展示在界面上的对象。React 非常灵活,但它也有一个严格的规则:所有 React 组件都必须像纯函数一样保护它们的 props 不被更改。
但是UI是动态的,我们为了保证这种动态性,React就引入了
state
来做组件的状态管理
-
state
-
概述:
我们之前所接触到的组件想要被更新只有通过
Reader()
函数的调用才能被更新,那么有没有可能我们让组件自己更新,而不用每次我们都显式的调用Render()
函数呢,答案是肯定的,其实传入组件的props
只要发生改变那么组件就会发生更新,但是props
对于子组件来说只是可读的,子组件不能管理,只有父组件改变传入的props
改变了才会更新,这样对于我们来说是非常不友好的,我们希望我们定义的组件(例如之前的时钟组件)有自己可以管理的状态,并且这个状态发生改变组件也能更新,这个状态就是我们接下来要说的state
。 -
特点:
state
是组件私有的,只有组件可以访问得到,对于外部不可见,组件与组件之间的state
也是互不影响的。同时state
完全受控于当前的组件。但是要注意,只有用class
定义的组件才有state
,所以类组件也被叫做有状态组件,函数组件是没有state
的,因此也被叫做无状态组件 -
使用
state
复写之前的时间组件// html 容器 <div id="root"></div> // js class Clock extends React.Component { constructor(props) { super(props) this.state = { date : new Date(), } }; componentDidMount(){ // 当 Clock 组件第一次被渲染到 DOM 中的时候,就为其设置一个计时器。这在 React 中被称为“挂载(mount)”。 this.timer = setInterval(() => { this.setState({ // 更新state date: new Date(), }) }) }; componentWillUnmount() { // 当 DOM 中 Clock 组件被删除的时候,应该清除计时器。这在 React 中被称为“卸载(unmount)”。 clearInterval(this.timer) } render() { return ( <div> <h1>Hello World!</h1> <h2>Now it is { this.state.date.toLocaleTimeString() }</h2> </div> ); } } ReactDOM.render(<Clock />, document.getElementById('root'))
梳理一下调用流程:
- 当
<Clock />
被传给ReactDOM.render()
的时候,React 会调用Clock
组件的构造函数。因为Clock
需要显示当前的时间,所以它会用一个包含当前时间的对象来初始化this.state
。我们会在之后更新 state。 - 之后 React 会调用组件的
render()
方法。这就是 React 确定该在页面上展示什么的方式。然后 React 更新 DOM 来匹配Clock
渲染的输出。 - 当
Clock
的输出被插入到 DOM 中后,React 就会调用ComponentDidMount()
生命周期方法。在这个方法中,Clock
组件向浏览器请求设置一个计时器来每秒调用一次组件的tick()
方法。 - 浏览器每秒都会调用一次
tick()
方法。 在这方法之中,Clock
组件会通过调用setState()
来计划进行一次 UI 更新。得益于setState()
的调用,React 能够知道 state 已经改变了,然后会重新调用render()
方法来确定页面上该显示什么。这一次,render()
方法中的this.state.date
就不一样了,如此以来就会渲染输出更新过的时间。React 也会相应的更新 DOM。 - 一旦
Clock
组件从 DOM 中被移除,React 就会调用componentWillUnmount()
生命周期方法,这样计时器就停止了。
这里面有几个关键点:
- 必须在
constructor
里面调用super(props)
,虽然使用super()
也可以,但是可能会出现问题,解释为什么需要:调用super()
,这是因为子类自己的this
对象,必须先通过父类的构造函数完成塑造,得到与父类同样的实例属性和方法,然后再对其进行加工,加上子类自己的实例属性和方法。如果不调用super方法,子类就得不到this
对象。所以必须先调用super
才可以使用this
。如果子类没有定义constructor
方法,这个方法会被默认添加。那么为什么要调用super(props)
,因为我们调用组件的时候会实例化一个对象,调用这个父类的component
会默认帮我们把props
添加到this
实例上,方便我们在自定义组件使用。 友链 - 组件的生命周期:生命周期是
React
中的一个重要概念,react
官方提供了很多生命周期的钩子函数供我们使用,为我们开发带来了很大的遍历,关于详细的生命周期我会在另一篇博客单独讲
使用
State
要注意的点-
不要直接修改state
this.state.date = new Date(); // 这种方法唯一能生效的情况就是在构造函数`constructor`中,其余情况均不生效。构造函数是唯一可以给 this.state 赋值的地方: this.setState({ date = new Date() }); // 这是在构造函数之外更改state属性的唯一方法
-
State 的更新可能是异步的
出于性能考虑,React 可能会把多个
setState()
调用合并成一个调用。因为
this.props
和this.state
可能会异步更新,所以你不要依赖他们的值来更新下一个状态。考虑这个例子:
class Clock extends React.Component { constructor(props) { super(props) this.state = { date : 11, } }; componentDidMount(){ this.setState( {date: this.state.date+1} ); this.setState( {date: this.state.date+1} ); this.setState( {date: this.state.date+1} ); this.setState( {date: this.state.date+1} ); this.setState( {date: this.state.date+1} ); this.setState( {date: this.state.date+1} ); }; render() { return ( <div> <h1>Hello World!</h1> <h2>Now it is { this.state.date }</h2> </div> ); } }
最后页面上显示的结果为:
这个例子可以清楚的看到state的更新是合并了的,要解决这种情况我们可以使用在setState()中使用回调函数的方式来更新我们的state值。
this.setState(function(state, props) { return { date: state.date + props.date }; }); // 这个函数用上一个 state 作为第一个参数,将此次更新被应用时的 props 做为第二个参数:
推荐文章
-
State 的更新会被合并
例如,你的 state 包含几个独立的变量:
constructor(props) { super(props); this.state = { posts: [], comments: [] }; }
然后你可以分别调用
setState()
来单独地更新它们:componentDidMount() { fetchPosts().then(response => { this.setState({ posts: response.posts }); }); fetchComments().then(response => { this.setState({ comments: response.comments }); }); }
这里的合并是浅合并,所以
this.setState({comments})
完整保留了this.state.posts
, 但是完全替换了this.state.comments
。
- 当
-
数据是从上而下流动的(单向的)
任何的 state 总是所属于特定的组件,而且从该 state 派生的任何数据或 UI 只能影响树中“低于”它们的组件。如果你把一个以组件构成的树想象成一个 props 的数据瀑布的话,那么每一个组件的 state 就像是在任意一点上给瀑布增加额外的水源,但是它只能向下流动。之后我们使用Redux的时候,只用在根组件使用
<provider>
就可以让整个子组件都是用到我们自己定义的store
;
未完待续… …