Bytedance Happiness Team [school recruitment/social recruitment/internship] is in the process of synchronization, [front-end/back-end/client/testing/data warehouse/algorithm/product/operation] has a large number of HC, waiting for you to do things together.

Xinlinli is bytedance’s real estate information, service and transaction platform that integrates content, community and tools. Based on the personalized recommendation engine, the product recommends high-quality real estate content and comprehensive and real housing information to users, and is committed to providing users with comprehensive, professional and reliable purchase decision support. Founded in August 2018, Happy Lane is the fastest growing real estate information and service platform in China, integrating content, community and tools. Its business covers 23 cities in the first and second tier, with tens of millions of registered users and entering a period of rapid growth.


Code article

When you want to write if… else… You may even need the ternary operator
// if... else...
if(someCondition){
   return a;
}else{
   return b;
}

// The ternary operator
return someCondition ? a : b;
Copy the code
When if does not need to handle else, it may even be necessary to short-circuit
// Conditional statement
if(conditionA){
   doSomething();
}

/ / short circuit
conditionA && doSomething();
Copy the code
When the if condition judgment is long, perhaps even more need to use Array. Some | Array. Every | Array. Includes
if(a === 1 || a === 2 || a ===3 || a ===4) {//....
}

// Easier to read
if([1.2.3.4].includes(a)){
  //....
}

// Multi-conditional statements
if(condition1 && condition2 && condition3){
   / /...
}

// Easier to read
const conditions = [condition1, condition2, condition3];
if(conditions.every(c= >c)){
   / /...
}

// Multiple conditions or
if(condition1 || condition2 || condition3){
   / /...
}

// Easier to read
const conditions = [condition1, condition2, condition3];
if(conditions.some(c= >c)){
   / /...
}
Copy the code
When you want to write for loops, you may want to call the native methods of arrays, such as Map,reduce, etc., and focus on the result of the data you want, rather than the process inside the for loop
/ / a for loop
for(let i = 0; i < data.length; i++){
   / /...
}

// If the purpose is array mapping, that is, the array length does not change before and after the function is executed
const result = data.map(item= >{
    return newItem
})

// If the purpose is data aggregation, i.e. the result of the function is a single value, the initial value and the result type are the same (empty array, empty object, 0, etc.).
const result = data.reduce((prev, cur) = >{
    //....
    returnnewResult; }, {})Copy the code
When you want to write switch… In case, dictionary mode may be more necessary
// switch... case
switch(a){
  case 1:
     b = 60;
     break;
  case 2:
     b = 70;
     break;
  case 3:
     b = 80;
  default:
     b = 90;
}

// dictionary mode
const valueMap = {
   1:60.2:70.3:80
}
const result = valueMap[a] ?? 90;
Copy the code
When you want to validate an Array with the _. IsArray provided by LoDash, use array. isArray
When you want to manipulate dates manually, you may need to read themDayjsThe document
When you want to divide a piece of complex logic into synchronous steps to execute, the FP mode of data + pure function may be more desirable
// multistep function
function doSomething(data){
   / /...
   return step1(data);
}

function step1(data){
   / /...
   return step2(data);
}

function step2(data){
    / /...
    return step3(data)
}

function step3(data){
    / /...
}

// Easier to read
function doSomething(data){
   const data1 = step1(data);
   const data2 = step2(data1);
   const data3 = step3(data2);
}

// Easier to read
function doSomething(data){
   [step3, step2, step1].reduceRight((prev,cur) = >cur(prev),data);
}

// Easier to read (separate data and logic)
const compose = (. args) = >data= >args.reduceRight((prev, cur) = >cur(prev),data);
const doSomething = compose([step3, step2, step1]);
Copy the code
Async /await or concatenated promises may be more necessary when there are multiple asynchronous functions in a complex piece of logic
// multistep function
function doSomething(data){
   step1(data).then(resp= >{
      / /... Resp judgment logic
      step2(resp.data).then(resp= >{
          / /... Resp judgment logic
          step3(resp.data).then(resp= >{
              / /... Resp judgment logic
              / /...}); })})}// Easier to read
async function doSomething(data){
   try{
      / /...
      const resp = await step1(data);
      const resp1 = await step2(resp.data);
      const resp2 = await step3(resp1.data);
      return resp2.data;
   }catch(err= >{
      console.log(err); })}// Easier to read
function doSomething(data){
   step1(data)
   .then(resp= >step2(resp.data))
   .then(resp= >step3(resp.data))
   .catch(err= >{
      console.log(err);
   });
}
Copy the code
When you want to remove an attribute from an object, use the destruct assignment and extension operators
// Delete attributes
function deleteA(obj){
  delete obj.A
  return obj;
}

// Use destruct assignment
const deleteA = ({A, ... rest} = {}) = > rest;
Copy the code
Write a separate function when you want to validate or reorganize the request parameters before sending a request

If the data needs to be processed into the format required by the interface before sending a request, the conversion function name suffix is ATOB

The conversion function name suffix is BTOA when the back-end response data needs to be processed again before it can be used

// The data needs to be processed again
const createInfo = (data) = >{
   / /... A bunch of data reprocessing operations
   someService.create(newData);
}

// Write the recombination function separately. (Recombination functions are also called transformer functions and are generally pure functions.)
import { someServiceCreate_atob, someServiceCreate_btoa } from 'transformer.ts'
const createInfo = async (data) =>{
   / /... A bunch of data reprocessing operations
   await response = someServiceCreate_btoa(someService.create(someServiceCreate_atob(data)));
}
Copy the code
You may need to extend operators and comma expressions when you don’t want to write blocks of code in a single line
//
const result = data.reduce((prev, cur) = >{
    prev[cur.key] = cur.value;
    returnprev; }, {});// Use comma expressions
const result = data.reduce((prev, {key, value}) = >(prev[key] = value, prev),{});
Copy the code
When you want to reference variables in an outer scope, you may want to return new objects to avoid contamination data.
// Not recommended
const out = {
  name: 'Tony Stark'
}
const handleChange = () = >{
   out.age = 56;
}

// The recommended way to write it
const handleChange = (info = {}) = >{
  return {
     ...info,
     age:56}}const result = handleChange(out);
Copy the code

JieGouPian

General rule: the UI layer code should be minimal, the logic layer code should only declare the main business logic actions, and other details should be defined externally.

MVC architecture — UI and state separation

Both MVC and MVVM hope to achieve the purpose of layering and decoupling through the constraints of code structure. Vue is naturally separated from “UI-style-behavior”, so it is not a problem to write. React advocates JSX write everything, cause a lot of new people to ignore the necessity of decoupling, all code directly {} expression a shuttle, lead to the maintenance time was watching UI problem, then he suddenly come in a few lines of business logic, the business logic when suddenly the door a few rows of data structure restructuring of the code, the code written in the maintenance cost is very high.

Reference answer:

** Code is divided into 4 layers: UI layer, state layer, service layer, and helper layer. Think about the type of code you are writing.

  • Try to write declarative code at the UI level (index.tsx) that states which state attributes in the store the UI depends on, and which methods in the store should be triggered by interaction actions.
  • The state layer (store.tsx) defines the states that the page needs to record, defines the methods that interaction actions should trigger, calls the service layer code to send requests when writing the method body, and calls the auxiliary layer code to process or validate data.
  • The service layer (service.tsx) aggregates business model methods, fetching data from behind the scenes.
  • FuZhuCeng (helper. The TSX/transformer. The TSX, etc.) defined data structure is used to change the pure functions and other do not belong to the former three types of code.

Separation of production and consumption

If you were now asked to fetch data from the background and render it after some processing, many novices would write code like this:

<template>
   <div v-for="item in items" v-if="item.show">
       <span>{{ item.value }}</span>
   </div>
</template>

<script>
  export default {
     name:'dealPage'.data(){
        return {
           items[]}}, mounted:function(){
        this.getList();
     },
     methods: {getList:function(){
            fetch('/getlist').then(data= >{
               this.items = data.items; }); }}}</script>
Copy the code

Functionally, the above code does work, but there are several potential problems:

  1. v-forandv-ifThere is a certain performance risk when used together, the specific can be baidu
  1. {{ item.value }}Is the code that consumes the data, andv-if="item.show"It’s essentially code that processes data, and the code that processes data theoretically shouldn’t be in UI code, we’d ratherThe UI layer, as the data consumer, gets the data directly available.
  1. It doesn’t scale well, and if there’s too much data now and you need front-end paging or virtual lists, the pattern of the code above would need to be changed.

Reference answer:

<template>
   <div v-for="item in displayItems">{{ item.value }}</div>
</template>

<script>
  export default {
     name:'dealPage'.data(){
        return {
           items: []}},computed: {displayItems:function(){
         return this.items.filter(item= >item.show); }}, mounted:function(){
        this.getList();
     },
     methods: {getList:function(){
            fetch('/getlist').then(data= >{
               this.items = data.items; }); }}}</script>
Copy the code

The nature of the data processing of code is transferred to the computed, extensibility will increase, and the subsequent no matter how complex logic, literally drummed up, go out as long as to ensure that the results of the final return, data processing of code, of course, if too complicated or placed in a separate file is better, so that we can keep the top of the UI and business logic code includes only the core logic, It’s cleaner and easier to maintain. React works similarly, especially when using {} to write expressions. Try not to mix in data processing code and use it only to describe the UI. I hope you can develop a sense of “post-natal consumption”, process data first, and then bind it to UI; Public components and methods are defined and then called in your own code. It is also necessary to notify others of the added public code in the group.

Middleware pattern

The middleware pattern is very useful, and those interested can explore the middleware actuator section of the Express, KOA2, or Redux source code.

Such as business logic in the need to create a transaction record, but the actual product requirements can be very tedious, front end need to be done with the three fields of ABC legitimacy check, sends a request to call DEF the three interfaces to pre check, after all through, can invoke the create interface to create transaction records, the appearance of the new code is likely to be written as the following:

create(formData){
   // check the A parameter
   if(CheckA(formData)){
      message.error(/ *... * /);
      return;
   }
   
   // check the B parameter
   if(checkB(formData)){
      message.error(/ *... * /);
      return;
   }
   
   // check the C parameter
   if(checkC(formData)){
      message.error(/ *... * /);
      return;
   }
   
   // Concatenate parameters
   const requestParams = {
     / /...
   }
   
   // Verify D on the server
   DealService.D(requestParams).then(resp= >{
       if(resp.status){
          this.E(resp.data);
       }
   }).catch(err= >{
       message.error(/ *... * /);
   });
},
// Verify E on the server
E(data){
   DealService.E(data).then(resp= >{
    if(resp.status){
          this.F(resp.data);
       }
   }).catch(err= >{
       message.error(/ *... * /);
   });
}

// The server validates F
F(data){
   DealService.F(data).then(resp= >{
    if(resp.status){
          this.realCreate(resp.data);
       }
   }).catch(err= >{
       message.error(/ *... * /);
   });
}

// Create a transaction record after verification
realCreate(){
   DealService.create(data).then(resp= >{
      if(resp.status){
         this.data = resp.data;
      }
   }).catch(err= >{
       message.error(/ *... * /);
   });
}
Copy the code

The code itself does run, but its problems are obvious:

  1. The reading experience is not good enough. The create function contains too much content. If something goes wrong during debugging, it is difficult to quickly locate a specific code segment and you may need to console.log line by line to see what went wrong.
  1. The scalability is also not good enough. If the verification item G is added and the back-end verification item D is changed to H, the developer will need to modify the original code a lot.
  1. DEF is also defined in store for convenience, but they don’t actually need to interact with the UI layer. The way they are written now interferes with understanding the main logic. After 3 or 4 asynchronous method hops in a row, you may not remember why you called this method. The details of service layer code should not be expanded in state layer code.

Reference answer:

  • Separate the validation function definition from the validation function call (the validation function execution can be combined with the form submission method) for subsequent convenience
  • Separate the definition of asynchronous methods from the invocation of asynchronous methods
  • Separate the orchestration of asynchronous methods from the invocation of asynchronous methods (an asynchronous executor is required, just refer to the implementation of express or KOA2 middleware executor)
Validators are defined in validators. TSX
function checkA(data={}){
  / /... Verifying A property
  if(/* Passes the verification */) {return null;
  }
  return 'Here's an error message from A.'
}

function checkB(data={}){
  / /... Verify B attribute
  if(/* Passes the verification */) {return null;
  }
  return 'Here's an error message for B.';
}

function checkC(data={}){
  / /... Verify C attribute
  if(/* Passes the verification */) {return null;
  }
  return 'Here's an error message for C.';
}

export const validators = [checkA,checkB,checkC];
Copy the code
// Transformer. TSX defines data structure conversion functions
export const D_atob = (data) = >{
   const requestParams = {
     //....
   }
   return requestParams;
}

export const E_atob = (data) = > {
   const requestParams = {
     //....
   }
   return requestParams;
}
Copy the code
// store. TSX references utility functions from other modules
import { validators } from './validators.tsx';
import { E_atob, D_atob } from './transformers.tsx';

/ /...
async create(formData){
   // Synchronize the verification logic
   for(const validator of validators){
       const flag = validator(formData);
       if(flag){
          message.error(flag);
          return; }}// The server validates and commits
   try{
       const Dresponse = await DealService.D(D_atob(formData));
       const Eresponse = await DealService.E(E_atob(Dresponse));
       const Fresponse = await DealService.F(Eresponse);
       const result = await DealService.create(Fresponse);
   }catch(err= >{ message.error(err? .message ||JSON.stringify(err));
   });
}
//....
Copy the code

If you define a compose function, receiving an array method that allows both synchronous asynchronous can be executed in sequence (synchronous error function or check failure can throw an error, is captured by the outer processing), then the service method arrangement and execution can also be separated (functional programming style of pipeline function is commonly from right to left, because call ShiChuanCan in most the right side, Visually more intuitive), the Store layer only needs to declare a createDeal method, the steps and details of which can be defined elsewhere:

const queue = compose([DealService.create, 
                       DealService.F,
                       DealService.E,
                       E_atob,
                       DealService.D, 
                       D_atob]);
try{
   queue();
}catch(err= >{ message.error(err? .message ||JSON.stringify(err));
});
Copy the code

The compose function is composed without asynchronism, so the custom checksum logic can be grouped together:

// The server validates and commits
const queue = compose([DealService.create, 
                       DealService.F,
                       DealService.E,
                       E_atob,
                       DealService.D, 
                       D_atob,
                       ...validators]);
try{
   queue();
}catch(err= >{ message.error(err? .message ||JSON.stringify(err));
});
Copy the code

Publish and subscribe model

Code first:

function doSomething(type){
    switch (type){
        case 1:
        / /...
        break;
        case 2:
        //....
        break;
        default:
        //....}}Copy the code

When there is complex interaction logic in the UI, the amount of code in the doSomething function often grows quickly and becomes difficult to maintain, and doSomething needs to be modified every time there is a new branch, which is against the “open and closed principle”.

Reference answer:

/* Use publish subscribe to split code */

// Define the callback for the specified event
eventEmitter.on('show-modal1'.function(data){
   // Data is the data passed in when the trigger method is called
})

eventEmitter.on('show-modal2'.function(data){
   //....
})

// Trigger the event
function doSomething(type,data){
   eventEmitter.trigger(type,data);
}
//
Copy the code

The trigger method is responsible for producing data related to incoming events, and the ON method is responsible for describing how to use the data. The code doesn’t have to be bunched together as long as eventEmitter uses the same singleton. Whenever new logic is added, the ON method is called to register the callback. There is no need to modify the doSomething function.