初识React
步骤一:添加一个DOM容器到HTML
<div id="app"></div>
步骤二:添加Script标签
<script src="https://unpkg.com/react@16/umd/react.development.js" crossorigin></script>
<script src="https://unpkg.com/react-dom@16/umd/react-dom.development.js" crossorigin></script>
<script src="app.js"></script>
步骤三:创建一个React组件
const e = React.createElement;
class LikeButton extends React.Component {
constructor(props) {
super(props);
this.state = {
liked: false
};
}
render () {
if (this.state.liked) {
return 'You liked this.';s
}
return e(
'button',
{onClick: () => this.setState({
liked: true
})},
'Like'
)
return (
<button onClick={() => this.setState({
liked: true})}
>
Like
</button>
)
}
}
const domContainer = document.querySelector('#app');
ReactDOM.render(e(LikeButton), domContainer);
添加JSX脚本
<script src="https://unpkg.com/babel-standalone@6/babel.min.js"></script>
创建新的React应用
npx create-react-app my-app
cd my-app
npm start
JSX 了解
const name = 'heqiuyu';
const element = <h1>Hello, {name}</h1>;
ReactDOM.render(
element,
document.getElementById('app')
);
function formatName (user) {
return user.firstName + ' ' + user.lastName;
}
const user = {
firstName: 'heqiuyu',
lastName: 'baixiaoyun'
};
const element = (
<h1>Hello, {formatName(user)}</h1>
);
ReactDOM.render(
element,
document.getElementById('app')
);
JSX 特定属性
const ele = <div tabIndex="0"></div>
const ele = <img src={user.avatarUrl} />
元素渲染
<div id="app"></div>
const ele = <h1>Hello, world</h1>
ReactDOM.render(ele, document.getElementById('app'));
更新已渲染的元素
function tick () {
const ele = (
<div>
<h1>Hello, world</h1>
<h2>It is {new Date().toLocaleTimeString()}</h2>
</div>
)
ReactDOM.render(
ele,
document.getElementById('app')
);
}
setInterval(tick, 1000);
组件 & Props
组件,从概念上类似于 JavaScript 函数。它接受任意的入参(即 “props”),并返回用于描述页面展示内容的 React 元素
函数式组件
function Welcome (props) {
return <h1>Hello, {props.name}</h1>;
}
类组件
class Welcome extends React.Component {
render () {
return <h1>Hello, {this.props.name}</h1>;
}
}
渲染组件
const ele = <Welcome name="heqiuyu" />;
当 React 元素为用户自定义组件时,它会将 JSX 所接收的属性(attributes)以及子组件(children)转换为单个对象传递给组件,这个对象被称之为 “props”
function Welcome (props) {
return <h1>Hello, {props.name}</h1>;
}
const ele = <Welcome name="heqiuyu" />;
ReactDOM.render(
ele,
document.getElementById('app')
);
提取组件
将组件拆分为更小的组件
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>
)
}
该组件用于描述一个社交媒体网站上的评论功能,它接收 author(对象),text (字符串)以及 date(日期)作为 props。
下面就来提取组件
function Avatar (props) {
return (
<img className="Avatar"
src={props.user.avatarUrl}
alt={props.user.name}
/>
)
}
function UserInfo (props) {
return (
<div className="UserInfo">
<Avatar user={props.user} />
<div className="UserInfo-name">
{props.user.name}
</div>
</div>
)
}
State & 生命周期
在元素渲染章节中,我们只了解了一种更新 UI 界面的方法。通过调用 ReactDOM.render() 来修改我们想要渲染的元素:
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('app')
);
正确地使用State
1.不要直接修改State
this.state.comment = 'Hello';
this.setState({comment: 'Hello'});
2. State的更新可能是异步的
this.setState({
counter: this.state.counter + this.props.increment,
});
要解决这个问题,可以让 setState() 接收一个函数而不是一个对象。这个函数用上一个 state 作为第一个参数,将此次更新被应用时的 props 做为第二个参数:
this.setState((state, props) => ({
counter: state.counter + props.increment
}));
3. State的更新会被合并
当你调用 setState() 的时候,React 会把你提供的对象合并到当前的 state
constructor(props) {
super(props);
this.state = {
posts: [],
comments: []
}
}
componentDidMount() {
fetchPosts().then(response => {
this.setState({
posts: response.posts
});
});
fetchComments().then(response => {
this.setState({
comments: response.comments
});
});
}
数据是向下流动的
重点:组件可以选择把它的 state 作为 props 向下传递到它的子组件中:
<FormattedDate date={this.state.date} />
function FormattedDate(props) {
return <h2>It is {props.date.toLocaleTimeString()}.</h2>;
}
事件处理
React事件的命名采用小驼峰式(camelCase),而不是纯小写 使用JSX语法时你需要传入一个函数作为事件处理函数,而不是一个字符串
<button onclick="handleclick()"></button>
<button onClick={handleClick}></button>
项事件处理程序传递参数
<button onClick={(e) => this.delete(id, e)}></button>
<button onClick={this.delete.bind(this, id)}></button>
条件渲染
在 React 中,你可以创建不同的组件来封装各种你需要的行为。然后,依据应用的不同状态,你可以只渲染对应状态下的部分内容。
与运算符&&
通过花括号包裹代码,你可以在JSX中嵌入任何表达式
function fn (props) {
const list = props.list;
return (
<>
{
list.length > 0
&&
<h2>渲染内容</h2>
}
</>
)
}
之所以能这样做,是因为在 JavaScript 中,true && expression 总是会返回 expression, 而 false && expression 总是会返回 false
三目运算符
语法
condition ? true : false
render () {
const isLogin = this.state.isLogin
return (
<>
the user is <b>{isLogin ? 'currently' : 'not'}</b> logged in.
</>
)
}
列表 & Key
基础列表组件
通常你需要在一个组件中渲染列表。
我们可以把前面的例子重构成一个组件,这个组件接收 numbers 数组作为参数并输出一个元素列表
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('app')
);
当我们运行这段代码,将会看到一个警告 a key should be provided for list items,意思是当你创建一个元素时,必须包括一个特殊的 key 属性
表单
受控组件
渲染表单的 React 组件还控制着用户输入过程中表单发生的操作
总的来说,这使得 , 和 之类的标签都非常相似—它们都接受一个 value 属性,你可以使用它来实现受控组件
处理多个输入
当需要处理多个 input 元素时,我们可以给每个元素添加 name 属性,并让处理函数根据 event.target.name 的值选择要执行的操作
this.setState({
[name]: value
})
var payLoad = {};
payLoad[name] = value;
this.setState(payLoad);
状态提升
通常,多个组件需要反映相同的变化数据,这时我们建议将共享状态提升到最近的共同父组件中去
无障碍辅助功能
语义化HTML
有时,语义化的 HTML 会被破坏。比如当在 JSX 中使用
元素来实现 React 代码功能的时候,又或是在使用列表(
-
,
-
和
)和 HTML 时。 在这种情况下,我们应该使用 React Fragments 来组合各个组件
import React, { Fragment } from 'react';
function ListItem ({item}) {
return (
<Fragment>
<dt>{item.term}</dt>
<dd>{item.description}</dd>
</Fragment>
{}
<>
<dt>{item.term}</dt>
<dd>{item.description}</dd>
</>
)
}
function Glossary (props) {
return (
<dl>
{
props.items.map(item => (
<ListItem item={item} key={item.id} />
))
}
</dl>
)
}
Context
Context 提供了一个无需为每层组件手动添加 props,就能在组件树间进行数据传递的方法
API
React.createContext
const MyContext = React.createContext(defaultValue)
创建一个 Context 对象。当 React 渲染一个订阅了这个 Context 对象的组件,这个组件会从组件树中离自身最近的那个匹配的 Provider 中读取到当前的 context 值。
只有当组件所处的树中没有匹配到 Provider 时,其 defaultValue 参数才会生效。这有助于在不使用 Provider 包装组件的情况下对组件进行测试。注意:将 undefined 传递给 Provider 的 value 时,消费组件的 defaultValue 不会生效。
Context.Provider
<MyContext.Provider value={ } >
每个 Context 对象都会返回一个 Provider React 组件,它允许消费组件订阅 context 的变化。
Provider 接收一个 value 属性,传递给消费组件。一个 Provider 可以和多个消费组件有对应关系。多个 Provider 也可以嵌套使用,里层的会覆盖外层的数据。
当 Provider 的 value 值发生变化时,它内部的所有消费组件都会重新渲染。Provider 及其内部 consumer 组件都不受制于 shouldComponentUpdate 函数,因此当 consumer 组件在其祖先组件退出更新的情况下也能更新
Class.contextType
class MyClass extends React.Component {
componentDidMount () {
let value = this.context
}
render () {
let value = this.context
}
}
MyClass.contextType = MyContext
挂载在 class 上的 contextType 属性会被重赋值为一个由 React.createContext() 创建的 Context 对象。这能让你使用 this.context 来消费最近 Context 上的那个值。你可以在任何生命周期中访问到它,包括 render 函数中在这里插入代码片
也可以使用public class fields语法
class MyClass extends React.Component {
static contextType = MyContext
render () {
let value = this.context
}
}
Context.Consumer
<MyContext.Consumer>
{value => }
</MyContext.Consumer>
高阶组件
含义:高阶组件是参数为组件,返回值为新组件的函数
const EnhancedComponent = higherOrderComponent(WrappedComponent);
组件是将props转换为UI,而高阶组件是将组件转换为另一个组件
Refs and the DOM
Refs 提供了一种方式,允许我们访问 DOM 节点或在 render 方法中创建的 React 元素
在典型的 React 数据流中,props 是父组件与子组件交互的唯一方式。要修改一个子组件,你需要使用新的 props 来重新渲染它。但是,在某些情况下,你需要在典型数据流之外强制修改子组件。被修改的子组件可能是一个 React 组件的实例,也可能是一个 DOM 元素。对于这两种情况,React 都提供了解决办法。
创建Refs
class MyComponent extends React.Component {
constructor(props) {
super(props);
this.myRef = React.createRef();
}
render () {
return <div ref={this.myRef} />
}
}
访问Refs
const node = this.myRef.current;
注意:ref 的值根据节点的类型而有所不同: 当 ref 属性用于 HTML 元素时,构造函数中使用 React.createRef() 创建的 ref 接收底层 DOM 元素作为其 current 属性。 当 ref 属性用于自定义 class 组件时,ref 对象接收组件的挂载实例作为其 current 属性。 你不能在函数组件上使用 ref 属性,因为他们没有实例。
为DOM元素添加ref
class CustomTextInput extends React.Component {
constructor(props) {
this.textInput = React.createRef();
}
focusTextInput () {
this.textInput.current.focus();
}
render () {
return (
<input
type="text"
ref={this.textInput}
/>
<input
type="button"
value="Focus the text input"
onClick={this.focusTextInput}
/>
)
}
}
React 会在组件挂载时给 current 属性传入 DOM 元素,并在组件卸载时传入 null 值。ref 会在 componentDidMount 或 componentDidUpdate 生命周期钩子触发前更新。
为class组件添加Ref
class AutoFocusTextInput extends React.Component {
constructor(props) {
super(props);
this.textInput = React.createRef();
}
componentDidMount () {
this.textInput.current.focusTextInput();
}
render () {
return (
<CustomTextInput ref={this.textInput} />
)
}
}
使用PropTypes进行类型检查
配置propTypes属性
import PropTypes from 'prop-types';
class Greeting extends React.Component {
render () {
return (
<h1>Hello, {this.props.name}</h1>
)
}
}
Greeting.defaultProps = {
name: 'Stranger'
};
Greeting.propTypes = {
name: PropTypes.string
};
应该在React组件的哪个生命周期函数中发起AJAX请求
我们推荐你在 componentDidMount 这个生命周期函数中发起 AJAX 请求。这样做你可以拿到 AJAX 请求返回的数据并通过 setState 来更新组件
使用 AJAX 请求结果去改变组件内部 state
{
"items": [
{"id": 1, "name": "Apples", "price": "$s"},
{"id": 2, "name": "Peaches", "price": "$5"}
]
}
class MyComponent extends React.Component {
constructor(props) {
super(props);
this.state = {
error: null,
isLoaded: false,
items: []
};
}
componentDidMount () {
fetch("https://api/example.com/itmes")
.then(res => res.json())
.then(
(result) => {
this.setState({
isLoaded: true,
items: result.items
});
},
(error) => {
this.setState({
isLoaded: true,
error
})
}
)
}
render () {
const { error, isLoaded, items } = this.state;
if (error) {
return <div>Error: {error.message}</div>
} else if (!isLoaded) {
return <div>Loading...</div>
} else {
return (
<ul>
{items.map(item =>(
<li key={item.name}>
{item.name} {item.price}
</li>
))}
</ul>
)
}
}
}
function MyComponent () {
const [error, setError] = useState(null);
const [isLoaded, setIsLoaded] = useState(false);
const [items, setItems] = useState([]);
useEffect(() => {
fetch("https://api.example.com/items")
.then(res => res.json())
.then(
(result) => {
setIsLoaded(true);
setItems(result.items);
},
(error) => {
setIsLoaded(true);
setError(error);
}
)
}, [])
if (error) {
return <div>Error: {error.message}</div>
} else if (!isLoaded) {
return <div>Loading...</div>
} else {
return (
<ul>
{items.map(item => {
<li key={item.name}>
{item.name} {item.price}
</li>
})}
</ul>
)
}
}
组件状态
setState实际做了什么?
setState() 会对一个组件的 state 对象安排一次更新。当 state 改变了,该组件就会重新渲染。
state和props之间的区别是什么?
props和state都是普通的JavaScript对象,他们都是用来保存信息的,不同的是:props是传递给组件的(类似于函数的形参),而state是在组件内被组件自己管理的(类似于在一个函数内声明的变量)(重点区别)
如何更新那些依赖于当前的state的state呢?
给 setState 传递一个函数,而不是一个对象,就可以确保每次的调用都是使用最新版的 state
setState什么时候是异步的?
目前,在事件处理函数内部的 setState 是异步的。
样式与CSS
如何为组件添加CSS的class?
render () {
return <span className="menu">Menu</span>
}
render () {
let className = 'menu';
if (this.props.isActive) {
className += ' menu-active';
}
return <span className={className}>menu</span>
}
|