React
technology

My React experience

React has dominated the front end for nearly three years, and the advantages it brings to data and UI binding have made it possible to move away from jQuery and DOM and focus on one-way data flow. We can break up a large Web app into small, independent, reusable components.

I met the State

Two years ago, I just got in touch with React and developed the first version of chat room by myself. At that time, I was still ignorant of state management. Father-son communication used props to pass values.

Contact Redux

Later when I met Redux, I was able to change reducer through actions and split reducer into different components, mapping different UIs, components and parent-child communication became more convenient, although the logic became clearer, the code also became more complex, write a set of Redux, It took at least three additional files to configure redux development, and it took some time, and the cost became prohibitively high.

Meet the Hooks

Now with react-hooks, we can make all class components a pure function. Because without class, you don’t have to use the bind or arrow functions for this. With hooks, your React code is much more pure. Clear and concise.

Why use react-hooks

But when an APP gets big enough, we often run into this problem

  1. Component commonality is often coupled with business logic
  2. When dealing with a lot of animation, foreign data, our components often become less sopure
  3. Too much logic and life cycle leads to confusion in state management
  4. Render and higher-order components lead to a very complicated DOM structure
  5. , etc.

With that said, let’s experience the magic of Hooks with practical examples.

Use react-hooks to develop chat rooms

The project source code is here, you can refer to the source code when reading this article, for webpack and socket. IO related, please refer to the first edition of the chat room

Write controlled components using useState

React uses a one-way data stream, so if you want to change the value in the input, you must bind an onChange event to the useState event

import React, { useState } from 'react'; / *... */ const userState = (username) => { const [user, setUsername] = useState(username); return [user, setUsername]; }; const App = (props) => { /* ... */ // const [user, setUsername] = userState(); / *... */ return <input type="text" placeholder=" placeholder "onChange={(e) => setUsername(e.target.value)} />; / *... * /};Copy the code

That is the simplest of hooks: we can define a state, declare a method that changes the state, and then manipulate that state by calling that method.

Use Context and useReducer to manage state

  • Context is a React method that allows you to manage data without passing it down the component tree level by level. See the React documentation for details
  • UseReducer is a Redux-like API provided by hooks that allow us to manage context, or state, using actions

Next we combine the two by creating a context/index.js file, adding the state we want to share, and passing the state and the dispatch method that changes the state to the child components via a ContextProvider.

Reducer defines three methods: LOGIN, update system message UPDATE_SYSTEM_MESSAGE (users enter or leave chat rooms), and update user message UPDATE_USER_MESSAGE (users send messages), which are used to update state.

import React, { createContext, useReducer } from 'react'; const Context = createContext(); const initValue = { username: '', uid: '', socket: io(), messages: [], onlineUsers: {}, onlineCount: 0, userhtml: '' }; function reducer(state, action) { switch (action.type) { case 'LOGIN': return { ... state, ... action.payload }; case 'UPDATE_SYSTEM_MESSAGE': return { ... state, ... { messages: state.messages.concat(action.payload.message) }, ... { onlineUsers: action.payload.onlineUsers }, ... { onlineCount: action.payload.onlineCount } }; case 'UPDATE_USER_MESSAGE': return { ... state, ... { messages: state.messages.concat(action.payload.message) } }; default: return state; } } const ContextProvider = (props) => { const [state, dispatch] = useReducer(reducer, initValue); return <Context.Provider value=>{props.children}</Context.Provider>; }; const ContextConsumer = Context.Consumer; export { Context, ContextProvider, ContextConsumer };Copy the code

Component Access Context

Once we’ve written the context, we put all the components that need to use the context into the children of context.provider, so we can get the state and method dispatch.

In SRC /container/ app. js, the key codes are as follows:

import React, { useContext, useState } from 'react'; import { Context } from '.. /context'; Const App = (props) => {// Get data in context const {state, dispatch} = useContext(context); const handleLogin = () => { const uid = generateUid(); const username = user ? User: 'visitor ${uid}'; dispatch({ type: 'LOGIN', payload: { uid, username } }); state.socket.emit('LOGIN', { uid, username }); }; / *... * /};Copy the code

The above code represents the dispatch method in the context when logging in, and transmits the uid and username parameters to the context

function reducer(state, action) { switch (action.type) { case 'LOGIN': return { ... state, ... action.payload }; / *... * /}}Copy the code

The uid and username of the user are stored in state after login.

In a chat room code SRC/container/ChatRoom. Js, similar logic, through socket to accept to the user login, logout, send messages, etc, to the context of the state, the specific code is as follows.

import React, { useContext, useState } from 'react'; import Messages from './Messages'; import ChatInput from './ChatInput'; import { Context } from '.. /context'; const ChatRoom = (props) => { const { state, dispatch } = useContext(Context); const [init, setInit] = useState(false); // Update system message const updateSysMsg = (o, action) => {const newMsg = {type: 'system', username: o.usser. username, uid: o.user.uid, action: action, msgId: generateMsgId(), time: generateTime() }; dispatch({ type: 'UPDATE_SYSTEM_MESSAGE', payload: { onlineCount: o.onlineCount, onlineUsers: o.onlineUsers, message: newMsg } }); }; // Send a new message const updateMsg = (obj) => {const newMsg = {type: 'chat', username: obj.username, uid: obj.uid, action: obj.message, msgId: generateMsgId(), time: generateTime() }; dispatch({ type: 'UPDATE_USER_MESSAGE', payload: { message: newMsg } }); }; Const ready = () => {const {socket} = props; setInit(true); socket.on('login', (o) => { updateSysMsg(o, 'login'); }); socket.on('logout', (o) => { updateSysMsg(o, 'logout'); }); socket.on('message', (obj) => { updateMsg(obj); }); }; if (! init) { ready(); } const renderUserList = () => { const users = state.onlineUsers; let userhtml = ''; let separator = ''; for (const key in users) { if (users.hasOwnProperty(key)) { userhtml += separator + users[key]; Separator = ', '; } } return userhtml; }; return ( <div className="chat-room"> <div className="welcome"> <div className="room-action"> <div The className = "room - the name" > fish head chat room | {props. The username} < / div > < div className = "button" > < button onClick = {() = > Window.location.reload ()}> </button> </div> </div> </div> <div className="room-status"> {state.onlineCount}, online list: {renderUserList()} </div> <div> <Messages messages={state.messages} myId={props.uid} /> <ChatInput myId={props.uid} myName={props.username} socket={props.socket} /> </div> </div> ); }; export default ChatRoom;Copy the code

In SRC/component/ChatRoom. Js, we access the context in the same way, retrieve messages from the state, apply colours to a drawing, we can finish the chat room core function!

Const Messages = (props) => {// Use the state in context instead of const {state} = useContext(context); const { uid, messages } = state; return ( <div className="messages" ref={messageList}> {messages.map((message) => ( <Message key={message.msgId} msgType={message.type} msgUser={message.username} action={message.action} isMe={uid == message.uid ? true : false} time={message.time} /> ))} </div> ); };Copy the code

UseEffect and useRef to play React more gracefully

Chat rooms often have a function, is to receive new messages, the page to keep up to date, how to accomplish this function? We can use useEffect and useRef here.

At one time, we needed to memorize the React lifecycle method and were often confused by which method to use. With the hooks useEffect method, we can set componentDidMount, UseEffect synthesized componentDidUpdate and componentWillUnmount.

// before
componentDidMount() {
  window.scrollTo(0, messageList.current.clientHeight + 50);
}
componentDidUpdate() {
  window.scrollTo(0, messageList.current.clientHeight + 50);
}
componentWillUnmount() {
  // DO SOMETHING
}

// after
useEffect(() => {
  window.scrollTo(0, messageList.current.clientHeight + 50);
  return () => {
    // DO SOMETHING
  }
});
Copy the code

It’s exactly the same. How about that? Is the code a lot leaner?

As for useRef, it’s even simpler to use

const messageList = useRef(null);

return (
  <div className="messages" ref={messageList}>
    {messages.map((message) => (
      <Message key={message.msgId} msgType={message.type} msgUser={message.username} action={message.action} isMe={uid == message.uid ? true : false} time={message.time} />
    ))}
  </div>
);
Copy the code

The combination of the above two should keep the latest news at the bottom as new messages arrive.

This makes it easy to use the DOM, and if you bind an input element and expose a ref to the parent element, you can also call methods like focus in the parent element.

Afterword.

After the above core code, our chat room is basically done, of course, we can view the specific details of the source code research.

After studying react-hooks, I came up with the following.

  • Say goodbye to the component nesting nightmare of Render Props and High Order Components
  • Instead of programming for the life cycle, you can program directly for the business (useEffect)
  • No more arrow functions or bind to this, no more Component, just purest functions

reference

  1. Making Sense of React Hooks
  2. Hooks Proposal
  3. Hooks FAQ
  4. React High Order Component Hell