这是别眨眼学前端的第 001 篇文章,别眨眼系列文章的主旨在于能够快速的回顾某个库/框架或是某种编程语言的基本知识点,于我而言作用类似于相对详细一点的复习大纲,用于快速回顾记忆,标题命名来源于 苹果 的视频 Don't blink
这一篇我们来快速回顾一下 React,本文是基于 React 官方文档 学习编写而成,可能会有描述不够清楚的地方,可自行参考原文
有关 React 全家桶的其余相关文章,可以查看以下链接,会持续更新
JSX
JSX
是 JavaScript 的一种语法扩展,在 React 中 JSX
用于描述 UI 看起来是什么样子的,JSX
产生出 React “元素”,之后会转换成 DOM 渲染在页面上
基本用法,
我们可以在 JSX
中使用 JavaScript 表达式 ,使用 {}
将 JavaScript 表达式包裹起来使用,
function formatName(user) { return user.firstName + ' ' + user.lastName; } const user = { firstName: 'Harper',lastName: 'Perez' }; const element = ( <h1> Hello,{formatName(user)}! </h1> ); ReactDOM.render( element,document.getElementById('root') );
在编译后,JSX
表达式会变成常规的 JavaScript 对象,这意味着我们可以在 if
语句和 for
循环中使用 JSX
,同时也可以将其声明为变量当做函数参数或者函数返回值,
function getGreeting(user) { if (user) { return <h1>Hello,{formatName(user.name)}!</h1>; } else { return <h1>Hello,Stranger.</h1>; } }
同时,JSX
为 React 的函数 React.createElement(component,props,...children)
的语法糖,
以下 JSX
代码,
<MyButton color="blue" shadowSize={2}> Click Me </MyButton>
会被编译为,
React.createElement( Mybutton,{color: 'blue',shadowSize: 2},'Click Me' )
当没有子元素的时候,我们可以使用自闭合标签的书写方式,
<div className="sidebar" />
会被编译为,
React.createElement( 'div',{className: 'sidebar'},null )
除了基本的引用方式之外,我可以使用对象属性值得方式来引用组件,这非常适用于在一个单独模块中导出多个组件,
const MyComponents = { DatePicker: function DatePicker(props) { return <div>Imagine a {props.color} datepicker here.</div>; } } function BlueDatePicker() { return <MyComponents.DatePicker color="blue" />; }
自定义组件必须以大写字母开始,
当一个元素或组件以小写字母开始的,它会和内置的组件比如 <div>
或是 <span>
一样以字符串的形式,'div'
或 'span'
传入 React.createElement
中,而以大写字母开头的自定义组件如 <Foo />
会被编译为 React.createElement(Foo)
并且同时会在你的 JS
文件中引用或者声明该自定义组件
Props in JSX
在使用 JSX
时有以下几种方式去声明一个组件的 porps
,
JavaScript 表达式,
我们可用 JavaScript 表达式来当做属性值,使用 {}
来包裹 JavaScript 表达式,
<MyComponent foo={1 + 2 + 3 + 4} />
对于组件 MyComponent
来说,它的属性 props.foo
的值为 10
,
if
语句与 for
循环在 JavaScript 中并非表达式,所以它们并不能在 JSX
中直接使用,一般我们这样使用,
function NumberDescriber(props) { let description; if (props.number % 2 == 0) { description = <strong>even</strong>; } else { description = <i>odd</i>; } return <div>{props.number} is an {description} number</div>; }
字符串字面量,
可以使用字符串字面量来当做组件的属性值,下面两个 JSX
的表达式结果是一样的,
<MyComponent message="hello world" /> <MyComponent message={'hello world'} />
属性值默认为 True
当我们在组建中传递一个没有值的属性时,它的默认值为 true
,以下两个 JSX
表达式的结果是一样的,
<MyTextBox autocomplete /> <MyTextBox autocomplete={true} />
Children in JSX
在 JSX
表达式中都会拥有一对闭合标签,闭合标签包裹的内容都会被传递进一个特殊的属性中:props.children
,通常有以下几种方式将值传递给 props.children
,
字符串字面量
<MyComponent>Hello world!</MyComponent>
组件 <MyComponent>
的属性 props.children
值为字符串 "Hello world!"
JSX Childern
同样,闭合标签内的子组件也会被传递给 props.children
,
<MyContainer> <MyFirstComponent /> <MySecondComponent /> </MyContainer>
JavaScript 表达式
同样,闭合标签内的 JavaScript 表达式也会被传递给 props.children
,一下两种 JSX
表达式的结果是一样的,
<MyComponent>foo</MyComponent> <MyComponent>{'foo'}</MyComponent>
函数表达式
function ListOfTenThings() { return ( <Repeat numTimes={10}> {(index) => <div key={index}>This is item {index} in the list</div>} </Repeat> ); } function Repeat(props) { let items = []; for (let i = 0; i < props.numTimes; i++) { items.push(props.children(i)); } return <div>{items}</div>; }
渲染元素
元素是 React app 中最小的组成模块,一个元素可以用以下方式描述,
const element = <h1>hello,world</h1>;
与浏览器 DOM 元素不同,创建或修改 React 元素的开销是极低的,我么使用 ReactDOM.render()
将 React 元素渲染至 DOM 节点中,
const element = <h1>Hello,world</h1>; ReactDOM.render( element,document.getElmentById('root') );
更新已渲染的元素
React 元素是不可变的,这意味着当我们创建出一个元素后,就不能改变他的子元素或是属性,它代表这某个时刻的 UI 所呈现出来的样子,
改变 UI 的方法之一是创建出一个新的元素并将它传递给 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);
通常 React app 中只会调用一次
ReactDOM.render()
方法,我们会在之后的章节中介绍怎样将 React 元素封装成为有状态的组件并通过状态的变化来实现 UI 的变化
组件与属性 (Components and Props)
组件允许我们拆分 UI 为独立的可复用的小块,概念上来讲,组件就像JavaScript 中的函数,它允许传递参数 (React 中称之为 Props
),并且返回 React 元素来表示什么应该显示在页面上,
功能组件和类组件 (Functional and Class Components)
定义一个组件最简单的方式就是使用 JavaScript 函数:
function Welcome(props) { return <h1>Hello,{porps.name}</h1>; }
上述函数是一个有效的组件,因为它传递了一个包含数据的 props
对象参数以及返回了 React 元素,我们称之为功能组件 (functional) 因为它就是 JavaScript 中的函数字面量 (literally JavaScript functions),
同样你可以使用 ES6 class 来定义一个组件,
class Welcome extends React.Component { render() { return <h1>Hello,{this.props.name}</h1>; } }
渲染组件
在这之前,我们遇到过一个 React 元素代表 DOM 标签的,
const element = <div />;
当然,一个 React 元素也可以代表一个自定义组件,
const element = <Welcom name="Sara" />;
当 React 遇到一个代表着自定义组件的元素的时候,会将 JSX
的属性作为一个单独的对象传递给该组件,我们称之为 props
,
下面代码中,结果是将 ”Hello,Sara” 渲染在了页面上:
function Welcome (props) { return <h1>Hello,{props.name}</h1>; } const element = <Welcome name="Sara" />; ReactDOM.render(element,document.getElementById('root'));
我们看看上述例子中发生了什么:
首先调用了
ReactDOM.render()
并传入<Welcome name="Sara" />
参数代表需要渲染的组件,React 调用
Welcome
组件并将{name: 'Sara'}
作为该组件的props
,Welcome
组件返回<h1>Hello,Sara</h1>
元素,React DOM 根据返回的元素去更新 DOM
当我们需要渲染一个组件多次的时候,可以这样编写,
function App() { return ( <div> <Welcome name="Sara" /> <Welcome name="Cahal" /> <Welcome name="Edite" /> </div> ); }
需要注意的是,所有组件必须包裹在唯一的根元素下,所以在上述例子中,我们将所有的
Welcome
组件包裹在<div>
下
props
为只读
React 遵守一条严格的规定:
所有的 React 组件相对于他们的 props
来说表现的都像是纯函数,在下节中我们会了解到 React 使用 state
来更新组件表现以便响应用户交互等
组件状态以及生命周期
在这之前,如果我们想更新已渲染后的 UI ,能够使用的方法就是再次调用 ReactDOM.render()
来重新渲染所有的组件输出,如下代码模拟了时间的变化,
function tick() { const element = ( <div> <h1>Hello,world!</h1> <h2>It is {new Date().toLocaleTimeString()}.</h2> </div> ); ReactDOM.render( element,1000);
State
然而我们并不希望这么做,正如之前提到的,一般情况下一个 React 应用只会调用一次 ReactDOM.render()
,所以当我们需要动态更新组件的时候,我们需要使用 state
,
只有类组件拥有 state
属性,功能组件不具备 state
属性,我们可以通过以下几步将一个功能组件转换成一个类组件,
将之前使用到
props
的地方替换为this.props
一个类似与上述例子的类组件如下,
class Clock extends React.Component { render() { return ( <div> <h1>Hello,world!</h1> <h2>It is {this.props.date.toLocaleTimeString()}.</h2> </div> ); } }
接下来我们会使用 state
来替代 props
,继而使组件能够动态更新,
将
this.props
替换为this.state
,
class Clock extends React.Component { render() { return ( <div> <h1>Hello,world!</h1> <h2>It is {this.state.date.toLocaleTimeString()}.</h2> </div> ); } }
添加
class constructor
来初始化组件state
,
class Clock extends React.Component { constructor(props) { super(props); this.state = {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') );
在使用 state
的时候有三点需要注意,
不要直接操作 state
,
// Wrong this.state.comment = 'Hello';
若要更改 state
,应该使用 setState()
方法,
this.setState({comment: 'Hello'});
state
的更新可能是异步的
处于性能的考虑,React 也许会批量处理一些 setState()
而统一进行一次更新,因为 this.props
和 this.state
也许会异步更新,所有不应依赖当前值去计算下一个状态,如,
// Wrong this.setState({ counter: this.state.counter + this.props.increment,});
正确的方法是,给 setState()
传递一个函数,这个函数接受两个参数,第一个参数表示上一个状态值,第二参数表示当前的 props
,
// Correct this.setState((prevState,props) => ({ counter: prevState.counter + props.increment }));
state
的更新会合并
当调用了 setState()
,React 会合并传入的更新的对象进当前的 state
中,例如,
constructor(props) { super(props); this.state = { posts: [],comments: [] }; } componentDidMount() { fetchPosts().then(response => { this.setState({ posts: response.posts }); // state 为 {posts: esponse.posts,comments: []} }); fetchComments().then(response => { this.setState({ comments: response.comments }); // state 为 {posts: esponse.posts,comments: response.comments} }); }
事件处理 (Handing Events)
React 中的事件处理和在原生 DOM 中事件处理是极为相似的,只是有以下细微的差别,
React 事件使用驼峰命名法,而非全部小写,
在使用
JSX
时,传递一个函数当做事件处理程序,而非一个字符串
比如,在 HTML 会这样编写,
<button onclick="activateLasers()"> Activate Lasers </button>
而在 React 中是这样的,
<button onClick={activateLasers}> Activate Lasers </button>
另一个不同点在与,在 React 中,不能使用 return false
来阻止原生组件的默认行为,必须使用 preventDefault
,
比如,在 HTML 中会这么编写,
<a href="#" onclick="console.log('The link was clicked.'); return false"> Click me </a>
而在 React 中是这样的,
function ActionLink() { function handleClick(e) { e.preventDefault(); console.log('The link was clicked.'); } return ( <a href="#" onClick={handleClick}> Click me </a> ); }
事件监听
在 React 中不应使用 addEventListener
来给 DOM 元素添加事件监听,一般会在初始化渲染的时候添加监听器,如下,
class Toggle extends React.Component { constructor(props) { super(props); this.state = {isToggleOn: true}; // This binding is necessary to make `this` work in the callback this.handleClick = this.handleClick.bind(this); } handleClick() { this.setState(prevState => ({ isToggleOn: !prevState.isToggleOn })); } render() { return ( <button onClick={this.handleClick}> {this.state.isToggleOn ? 'ON' : 'OFF'} </button> ); } } ReactDOM.render( <Toggle />,document.getElementById('root') );
组件生命周期
表单组件 (Form components)
对于表单组件来说,比如 <input>
, <textarea>
和 <option>
,和原生组件最大的区别表单组件会通过用户的交互来变化,表单控件为管理表单响应用户交互提供了接口
React 中的表单控件分为两种类型:
受控组件 (Controlled Components)
非受控组件 (Uncontrolled Components)
受控组件
受控组件会提供一个 value
属性,该组建并不会维护自身内部的状态,该组件的渲染纯粹基于它的属性,
render() { return <input type="text" value="Hello" /> }
用户输入并不会影响已渲染的元素,因为 React 已经将其的 value
属性声明为 Hello
,如果想让控件值随用户输入而改变,我们需要使用 onChange
事件,
class Form extends React.Component { constructor(props) { super(props); this.state = {value: ""}; this.handleChange = this.handleChange.bind(this); } handleChange(event) { this.setState({value: event.target.value}); } render() { return ( <div> <input type="text" placeholder="Hello!" value={this.state.value} onChange={this.handleChange} /> </div> ); } } ReactDOM.render(<Form />,document.getElementById('root'));
非受控组件
不提供 value
属性的表单空间我们称之为非受控组件,
render() { return <input type="text" /> }
上述例子中我们渲染了一个空值 <input>
控件,用户的任何输入都会直接翻译在已渲染的元素中,受控组级自己管理自己的状态,
默认值 (Default Values)
如果想要给非受控组件一个初始化默认值,可以使用 defaultValue
属性,
render() { return <input type="text" defualtValue="hello" /> }
Interactive Props
表单组件允许通过使用 onChange
属性来监听它的变化,一下情况下会触发 onChange
属性,
当
<input>
或<textarea>
的value
发生改变,当
<input>
的checked
状态发生改变,当
<option>
的selected
状态发生改变
和 DOM
事件相同,onChange
属性支持所有原生组件,并且可以监听冒泡事件
状态提升
当许多组件需要使用相同的状态时,将该状态提升至这些组件共有的最近的父级组件上,并用 props
传递给各个子组件,并使用回调函数的方式使得子组件能够改变父组件的状态
至此,别眨眼看 React 完结啦,