内容概要:
- Higher order component
- Ref
- context API
- Http / Ajax / axios
- Redux
1. Higher-order component
A higher-order component is a function that takes a component and returns a new component.
Example1:
//Inside the withClass
import React from 'react';
// const WithClass = props => (
// <div className={props.classes}> {props.children} </div>
// );
//now it is not a Component any more
//It is a normal javascript function
const withClass = (WrappedComponent, className) => {
return props => (
<div className={className}>
<WrappedComponent {...props}/>
</div>
)
}
export default withClass
// Use the higher order component in App.js
export default withClass(App, styles.App) ;
Example 2:
import React from 'react';
const Aux = (props) => props.children;
export default Aux
//Use it in App.js
return (
<Aux>
<button onClick={() => {this.setState({showCockpit: false});}}>Show Cockpit</button>
{this.state.showCockpit? <Cockpit
name = "SS"
peopleLength = {this.state.people.length}
showPersons = {this.state.showPerson}
switch = {this.switchNameHandler}
toggle = {this.togglePersonHandler}
showCockpit = {this.state.showCockpit}
/> : null}
{persons}
</Aux>
);
2. Ref
Refs stand for references. ref
keyword could be used on any element or component.
When to Use Refs:
There are a few good use cases for refs:
- Managing focus, text selection, or media playback.
- Triggering imperative animations.
- Integrating with third-party DOM libraries.
Creating Refs
class MyComponent extends React.Component {
constructor(props) {
super(props);
this.myRef = React.createRef();
}
render() {
return <div ref={this.myRef} />;
}
}
Accessing Refs
When a ref is passed to an element in render
, a reference to the node becomes accessible at the current
attribute of the ref.
const node = this.myRef.current;
The value of the ref differs depending on the type of the node:
- When the ref attribute is used on an HTML element, the ref created in the constructor with
React.createRef()
receives the underlying DOM element as its current property. - When the ref attribute is used on a custom class component, the ref object receives the mounted instance of the component as its current.
- You may not use the ref attribute on function components because they don’t have instances.
将current
property 在首次mounted时赋值为当前element,在unmounted时重新变为null
。 ref
的值在 componentDidMount
或 componentDidUpdate
之前更新
useRef()
在默认情况下,functional component 中不能使用ref。 然而可以使用 useRef()
React Hook 在functional component 中创建并初始化 ref
.
const Cockpit = (props) => {
const toggleBtnRef = useRef(null);
//useEffect works after every render cycle
// The functions inside will execute after the JSX loaded
useEffect(() => {
toggleBtnRef.current.click();
return () => {
console.log('[Cockpit.js] cleanup work in useEffect');
}
}, []);
return(
<div>
<button
className={btnClass}
onClick={props.toggle}
key = 'toggleBtn'
ref = {toggleBtnRef}
>TogglePerson</button>
</div>
);
}
以上例子中,使得button按钮在首次render
完成后,被默认点击一次。
3. Context API
Say we are in a case where we want to pass a prop from component A at the top to component D at the bottom. And we have components B and C in the middle, but they don't really care about the props, they will just transmit props.
It is useful when you don't want to pass props across multiple layers. It helps you skip the middle components.
import {React} from 'React';
const authContext = React.createContext({
authenticated: false,
login: () => {}
});
export default authContext;
React.createContext()
可以创建并初始化context的值,这个值可以是一个 JavaScript Object
, 也可以是 number, array, string
等等。你可以自定义这个值的有效范围, 让他在React Components中传递。
-
Provider & Consumer
在App.js中,将需要传递的值赋给在上例中已提前定义好的JS对象。使用<AuthContext.Provider></AuthContext.Provider>
包裹你需要使用此value的内容。
import AuthContext from '../context/auth-context' ;
<AuthContext.Provider
value= {{
authenticated: this.state.authenticated,
login: this.loginHandler}}>
{this.state.showCockpit? <Cockpit
name = "SS"
peopleLength = {this.state.people.length}
showPersons = {this.state.showPerson}
switch = {this.switchNameHandler}
toggle = {this.togglePersonHandler}
showCockpit = {this.state.showCockpit}
/> : null}
{persons}
</AuthContext.Provider>
<AuthContext.Consumer> </AuthContext.Consumer>
也与之类似,用它包裹相关的代码段,且包装为一个函数。由context
传入所需要的数值。
return(
<div className={styles.Cockpit}>
... Other content
<AuthContext.Consumer>
{(context) => <button onClick={context.login}>Login</button>}
</AuthContext.Consumer>
</div>
);
Another Way to use <AuthContext.Consumer>
. In this way, we can access the context in life cycle hooks like componentDidMount()
exected after render
.
In class-base components:
import AuthContext from '../../../context/auth-context';
class Person extends Component{
static contextType = AuthContext;
componentDidMount() {
console.log(this.context.authenticated);
}
}
render(){
return (
<Aux>
{this.context.authenticated ? <p>Authenticated!</p> : <p>Please Login !</p>}
... Other content
</Aux>
)
}
contextType
is a reserved word, you have to write exactly the same.
In functional components, you can use useContext
hook to achieve the same effect.
import AuthContext from '../../context/auth-context'
const Cockpit = (props) => {
const authContext = useContext(AuthContext);
console.log(authContext.authenticated);
return(
<div className={styles.Cockpit}>
<button onClick={authContext.login}>Login</button>
</div>
);
}
4.Http Request in React
When you send requests to a server, you won't get HTML pages back, instead, you will get JSON data.
Normally, your server is a RESTFUL API, just exposing some API endpoints to which you can send requests.
AJAX
axios
interceptors: that Axios calls for every request. You can use interceptors to transform the request before Axios sends it, or transform the response before Axios returns the response to your code.
It is useful for setting some common headers like authorization headers or handling errors globally.
// In index.js
axios.interceptors.request.use(request => {
console.log(request);
// always need to return the request, otherwise it blocks the request.
// you can edit the request before you return
return request;
}, error => {
console.log(error);
return Promise.reject(error);
});
axios.interceptors.response.use(response => {
console.log(response);
// Edit request config
return response;
}, error => {
console.log(error);
return Promise.reject(error);
})
Removing Interceptors (otherwise could cause leak memory):
var myInterceptor = axios.interceptors.request.use(function () {/*...*/});
axios.interceptors.request.eject(myInterceptor);
- fetch()
5. Routing
The basic idea is to load different component codes for different paths.
-
Implement:
installreact-router-dom
, then wrap the component, where you are going to use Router.
import { BrowserRouter } from 'react-router-dom';
class App extends Component {
render() {
return (
<BrowserRouter>
<div className="App">
<Blog />
</div>
</BrowserRouter>
);
}
}
-
Use <Route> component.
import {Route, Link} from 'react-router-dom';
It can be configured with several attributes.
1.path: start with the certain path
2.exact: exactly the same path
3.render: render some JSX code under this path
4.component: render a component under this path
<Route path="/" exact component={Posts}/>
<Route path="/new-post" exact component={NewPost}/>
-
Use
<Link></Link>
instead of<a/>
to avoid Application reload.to
attribute in <Link/> ashref
in <a/>
Create a router:
<header className="Blog">
<nav>
<ul>
<li><Link to="/">Home</Link></li>
<li><Link to="/new-post">New Post</Link></li>
</ul>
</nav>
</header>
You can also pass an Object to the to
attribute in <Link></Link>
<li><Link to = {{
pathname = '/new-post',
hash: 'submit',
search: '?quick-submit=true'
}}>New Post</Link></li>
- pathname: relate to the path you set in <Route/>. It is always treated as an absolute path.
- hash: Used to jump to a certain part of your page, or scroll smoothly there.
- search:
...
-
<NavLink>
可以使用<NavLink>添加activeStyle
attribute 来给Route添加样式。如果不需要样式,使用<Link></Link>即可。
<li><NavLink
to="/"
exact
activeClassName = "my-active"
activeStyle = {{
color: '#fa923f',
textDecoration: 'underline'
}}
>Home</NavLink></li>
整体代码:
import React, { Component } from 'react';
import {Route, Link} from 'react-router-dom';
import Posts from './Posts/Posts';
import NewPost from './NewPost/NewPost';
import './Blog.css';
class Blog extends Component {
render () {
return (
<div>
<header className="Blog">
<nav>
<ul>
<li><Link to="/">Home</Link></li>
<li><Link to="/new-post">New Post</Link></li>
</ul>
</nav>
</header>
{/* <Route path="/" exact render={() => <h1>Home</h1>}/> */}
{/* loading components */}
<Route path="/" exact component={Posts}/>
<Route path="/new-post" exact component={NewPost}/>
</div>
);
}
}
export default Blog;
运行效果:
-
withRouter
High Order Component
We use the HOC to wrap the export to make the exported component route aware. And it will get the props containing information for the nearest loaded route.
import {withRouter} from 'react-router-dom';
-
Absolute Path vs Relative Path
An absolute path is always appended to your domain. 在<Link>的 to attribute 中, 所有的path都默认为Absolute path.
Sometimes, you might want to create a relative path instead. This is especially useful if your component is already loaded given a specific path (e.g. posts ) and you then want to append something to that existing path (so that you, for example, get /posts/new ).
If you're on a component loaded via/posts
,to="new"
would lead toexample.com/new
, NOTexample.com/posts/new
.
使用以下方法可以获得dynamic path:
<Link to={props.match.url + '/new'}>
-
Route Parameters
传参(Pass Route Parameters):
<Link to={'/' + this.props.id}></Link>
提取参数(extract route parameters):
<Route path ="/:id" exact component={FullPost}/>
But how do you extract search (also referred to as "query") parameters (=> ?something=somevalue
at the end of the URL)? How do you extract the fragment (=> #something
at the end of the URL)?
Query Params:
You can pass them easily like this:
<Link to="/my-path?start=5">Go to Start</Link>
or
<Link
to={{
pathname: '/my-path',
search: '?start=5'
}}
>Go to Start</Link>
React router makes it easy to get access to the search string: props.location.search
.
But that will only give you something like ?start=5
You probably want to get the key-value pair, without the ?
and the =
. Here's a snippet which allows you to easily extract that information:
componentDidMount() {
const query = new URLSearchParams(this.props.location.search);
for (let param of query.entries()) {
console.log(param); // yields ['start', '5']
}
}
URLSearchParams
is a built-in object, shipping with vanilla JavaScript. It returns an object, which exposes the entries() method. entries()
returns an Iterator - basically, a construct which can be used in a for...of... loop (as shown above).
When looping through query.entries()
, you get arrays where the first element is the key name (e.g. start
) and the second element is the assigned value (e.g. 5
).
Fragment:
You can pass it easily like this:
<Link to="/my-path#start-position">Go to Start</Link>
or
<Link
to={{
pathname: '/my-path',
hash: 'start-position'
}}
>Go to Start</Link>
React router makes it easy to extract the fragment. You can simply access props.location.hash
.
-
Using Switch
Switch
tells React to only load the first Route that matches from a given set of routes.
<Switch>
<Route path="/" exact component={Posts}/>
<Route path="/new-post" exact component={NewPost}/>
<Route path ="/:id" exact component={FullPost}/>
</Switch>
When we wrap our routes within Switch
, and the first route that matches a given path will be loaded. After, it will just stop analyzing the routes, it won't render any other route.
-
Nested Route
It is a condition that you implement a Route inside a component rendered by a Route.
<Route path ={this.props.match.url + "/:id"} exact component={FullPost}/>
Use this.props.match.url
dynamically get parent url.
- Conditional Redirects
- Use
<Redirect/>
to redirect.
Use it as a component. Render it to leave the current page.
import {Redirect} from 'react-router-dom';
<Redirect to="/posts"/>
- Use
this.props.history.push("path")
orthis.props.history.replace("path")
to redirect.
Difference:push()
function push a new page on the satck, whereasredirect
andreplace()
function replace the current page.
-
Navigation Guard
It is usually used when you don't know whether the user is authenticated or not. Or in your application, there are some routes that you only allow authenticated users to access.
- Handle unknown routes 404
<Switch>
<Route path="/" exact component={Posts}/>
<Route path="/new-post" exact component={NewPost}/>
<Route path ="/:id" exact component={FullPost}/>
<Route render = {() => <h1>Not Found</h1>}/>
</Switch>
- Lazy Loading
Only loading the component once you need it.
Now the thing is whenever you are importing something with import
from somewhere.
you basically inform webpack, the build tool which gets used behind the scenes about this dependency and it will include it in the global bundle, this is its job.
Now for the lazy loading, this is exactly the opposite of what we want to do, we don't want to include it in the bundle, we want to load it when needed.
Still, webpack needs to be able to dynamically prepare some extra bundle for this potentially loaded code.
- Create a Higher-Order Component:
import React, {Component} from 'react';
const anycComponent = (importComponent) => {
return class extends Component{
state = {
component: null
}
componentDidMount(){
importComponent()
.then(cmp => {
this.setState({component: cmp.default});
});
}
render () {
const C = this.state.component;
return C ? <C {...this.props}/> : null;
}
}
}
export default anycComponent;
- import the component we want to dynamically load in this HOC.
import React, { Component } from 'react';
import {Route, NavLink, Switch} from 'react-router-dom';
import Posts from './Posts/Posts';
//import NewPost from './NewPost/NewPost';
import asyncComponent from '../../hoc/asyncComponent';
const AsyncNewPost = asyncComponent(() => {
return import('./NewPost/NewPost');
})
class Blog extends Component {
render () {
<div>
<Switch>
<Route path="/new-post" exact component={AsyncNewPost}/>
<Route path="/posts" component={Posts}/>
</Switch>
</div>
}
}
export default Blog;
React 16.0 版本及以上,可用React.lazy()
方法引入component,并使用<suspense></suspense>
component 将其包裹:
import React, { Component, Suspense } from 'react';
import { BrowserRouter, Route, NavLink } from 'react-router-dom';
import User from './containers/User';
import Welcome from './containers/Welcome';
const Posts = React.lazy(() => import('./containers/Posts'));
class App extends Component {
state = { showPosts: false };
modeHandler = () => {
this.setState(prevState => {
return { showPosts: !prevState.showPosts };
});
};
render() {
return (
<React.Fragment>
<button onClick={this.modeHandler}>Toggle Mode</button>
{this.state.showPosts ? (
<Suspense fallback={<div>Loading...</div>}>
<Posts />
</Suspense>
) : (
<User />
)}
);
}
}
export default App;
6. Redux
It is a library often used in React projects to manage application state.
There are four stages to building a Redux app:
1.Visualise the state tree
2.Design your reducers
3.Implement Actions
4.Implement Presentation