最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

前端面試手撕合集之純HTML、Vue和React組件

 更新時間:2026年02月28日 08:26:28   作者:FE_Jinger  
Vue 和React都是當前非常流行的前端框架,它們都可以用來構建單頁面應用(SPA)和響應式的用戶界面,這篇文章主要介紹了前端面試手撕代碼之純HTML、Vue和React組件的相關資料,需要的朋友可以參考下

一、原生JS 核心組件

1. 輪播圖(自動播放+點擊切換+指示器)

<!DOCTYPE html>
<html lang="zh-CN">
<head>
  <meta charset="UTF-8">
  <title>原生JS輪播圖</title>
  <style>
    .carousel { width: 500px; height: 300px; margin: 50px auto; position: relative; overflow: hidden; }
    .carousel-list { display: flex; width: 100%; height: 100%; transition: transform 0.5s ease; }
    .carousel-item { flex: 0 0 100%; width: 100%; height: 100%; }
    .carousel-item img { width: 100%; height: 100%; object-fit: cover; }
    .carousel-btn { position: absolute; top: 50%; transform: translateY(-50%); width: 40px; height: 40px; background: rgba(0,0,0,0.5); color: white; border: none; cursor: pointer; z-index: 10; }
    .prev { left: 10px; }
    .next { right: 10px; }
    .carousel-indicator { position: absolute; bottom: 20px; left: 50%; transform: translateX(-50%); display: flex; gap: 10px; }
    .indicator-item { width: 10px; height: 10px; border-radius: 50%; background: rgba(255,255,255,0.5); cursor: pointer; }
    .indicator-item.active { background: white; }
  </style>
</head>
<body>
  <div class="carousel">
    <div class="carousel-list">
      <div class="carousel-item"><img src="https://picsum.photos/500/300?1" alt="圖1"></div>
      <div class="carousel-item"><img src="https://picsum.photos/500/300?2" alt="圖2"></div>
      <div class="carousel-item"><img src="https://picsum.photos/500/300?3" alt="圖3"></div>
    </div>
    <button class="carousel-btn prev">←</button>
    <button class="carousel-btn next">→</button>
    <div class="carousel-indicator">
      <div class="indicator-item active"></div>
      <div class="indicator-item"></div>
      <div class="indicator-item"></div>
    </div>
  </div>

  <script>
    const carousel = document.querySelector('.carousel');
    const list = document.querySelector('.carousel-list');
    const items = document.querySelectorAll('.carousel-item');
    const prevBtn = document.querySelector('.prev');
    const nextBtn = document.querySelector('.next');
    const indicators = document.querySelectorAll('.indicator-item');
    
    const itemWidth = items[0].offsetWidth;
    let currentIndex = 0;
    let timer = null;

    // 初始化指示器
    function updateIndicator() {
      indicators.forEach((item, index) => {
        item.classList.toggle('active', index === currentIndex);
      });
    }

    // 切換輪播
    function switchCarousel(index) {
      currentIndex = index;
      list.style.transform = `translateX(-${currentIndex * itemWidth}px)`;
      updateIndicator();
    }

    // 下一張
    function next() {
      currentIndex = (currentIndex + 1) % items.length;
      switchCarousel(currentIndex);
    }

    // 上一張
    function prev() {
      currentIndex = (currentIndex - 1 + items.length) % items.length;
      switchCarousel(currentIndex);
    }

    // 自動播放
    function autoPlay() {
      timer = setInterval(next, 3000);
    }

    // 事件綁定
    nextBtn.addEventListener('click', next);
    prevBtn.addEventListener('click', prev);
    indicators.forEach((item, index) => {
      item.addEventListener('click', () => switchCarousel(index));
    });

    // 鼠標懸停暫停
    carousel.addEventListener('mouseenter', () => clearInterval(timer));
    carousel.addEventListener('mouseleave', autoPlay);

    // 啟動自動播放
    autoPlay();
  </script>
</body>
</html>

2. 下拉組件(點擊展開+點擊外部關閉)

<!DOCTYPE html>
<html lang="zh-CN">
<head>
  <meta charset="UTF-8">
  <title>原生JS下拉組件</title>
  <style>
    .dropdown { width: 200px; margin: 50px auto; position: relative; }
    .dropdown-trigger { width: 100%; padding: 10px; border: 1px solid #ccc; border-radius: 4px; cursor: pointer; }
    .dropdown-menu { width: 100%; padding: 0; margin: 0; list-style: none; border: 1px solid #ccc; border-top: none; border-radius: 0 0 4px 4px; position: absolute; top: 100%; left: 0; background: white; display: none; z-index: 999; }
    .dropdown-menu.show { display: block; }
    .dropdown-item { padding: 10px; cursor: pointer; }
    .dropdown-item:hover { background: #f5f5f5; }
  </style>
</head>
<body>
  <div class="dropdown" id="dropdown">
    <div class="dropdown-trigger">請選擇</div>
    <ul class="dropdown-menu">
      <li class="dropdown-item">選項1</li>
      <li class="dropdown-item">選項2</li>
      <li class="dropdown-item">選項3</li>
    </ul>
  </div>

  <script>
    const dropdown = document.getElementById('dropdown');
    const trigger = dropdown.querySelector('.dropdown-trigger');
    const menu = dropdown.querySelector('.dropdown-menu');
    const items = dropdown.querySelectorAll('.dropdown-item');

    // 切換顯示/隱藏
    function toggleMenu() {
      menu.classList.toggle('show');
    }

    // 點擊外部關閉
    document.addEventListener('click', (e) => {
      if (!dropdown.contains(e.target)) {
        menu.classList.remove('show');
      }
    });

    // 選擇選項
    items.forEach(item => {
      item.addEventListener('click', () => {
        trigger.textContent = item.textContent;
        menu.classList.remove('show');
      });
    });

    // 綁定觸發(fā)事件
    trigger.addEventListener('click', (e) => {
      e.stopPropagation(); // 防止事件冒泡觸發(fā)外部點擊
      toggleMenu();
    });
  </script>
</body>
</html>

3. 表單驗證(手機號+密碼+提交驗證)

<!DOCTYPE html>
<html lang="zh-CN">
<head>
  <meta charset="UTF-8">
  <title>原生JS表單驗證</title>
  <style>
    .form { width: 300px; margin: 50px auto; }
    .form-item { margin-bottom: 20px; }
    .form-label { display: block; margin-bottom: 5px; }
    .form-input { width: 100%; padding: 8px; border: 1px solid #ccc; border-radius: 4px; }
    .form-input.error { border-color: red; }
    .error-tip { color: red; font-size: 12px; margin-top: 5px; display: none; }
    .error-tip.show { display: block; }
    .submit-btn { width: 100%; padding: 10px; background: #007bff; color: white; border: none; border-radius: 4px; cursor: pointer; }
    .submit-btn:disabled { background: #ccc; cursor: not-allowed; }
  </style>
</head>
<body>
  <form class="form" id="loginForm">
    <div class="form-item">
      <label class="form-label">手機號</label>
      <input type="tel" class="form-input" id="phone" placeholder="請輸入手機號">
      <div class="error-tip" id="phoneTip">請輸入正確的11位手機號</div>
    </div>
    <div class="form-item">
      <label class="form-label">密碼</label>
      <input type="password" class="form-input" id="pwd" placeholder="請輸入6-16位密碼">
      <div class="error-tip" id="pwdTip">密碼長度需在6-16位之間</div>
    </div>
    <button type="submit" class="submit-btn" id="submitBtn" disabled>提交</button>
  </form>

  <script>
    const phoneInput = document.getElementById('phone');
    const pwdInput = document.getElementById('pwd');
    const phoneTip = document.getElementById('phoneTip');
    const pwdTip = document.getElementById('pwdTip');
    const submitBtn = document.getElementById('submitBtn');
    const loginForm = document.getElementById('loginForm');

    // 驗證手機號
    function validatePhone() {
      const phone = phoneInput.value.trim();
      const reg = /^1[3-9]\d{9}$/;
      if (!reg.test(phone)) {
        phoneInput.classList.add('error');
        phoneTip.classList.add('show');
        return false;
      }
      phoneInput.classList.remove('error');
      phoneTip.classList.remove('show');
      return true;
    }

    // 驗證密碼
    function validatePwd() {
      const pwd = pwdInput.value.trim();
      if (pwd.length < 6 || pwd.length > 16) {
        pwdInput.classList.add('error');
        pwdTip.classList.add('show');
        return false;
      }
      pwdInput.classList.remove('error');
      pwdTip.classList.remove('show');
      return true;
    }

    // 檢查所有驗證
    function checkAll() {
      const isPhoneValid = validatePhone();
      const isPwdValid = validatePwd();
      submitBtn.disabled = !(isPhoneValid && isPwdValid);
    }

    // 實時驗證
    phoneInput.addEventListener('input', checkAll);
    pwdInput.addEventListener('input', checkAll);

    // 提交驗證
    loginForm.addEventListener('submit', (e) => {
      e.preventDefault();
      if (checkAll()) {
        alert('表單驗證通過,提交成功!');
        loginForm.reset();
        submitBtn.disabled = true;
      }
    });
  </script>
</body>
</html>

4. 無限滾動(滾動到底部加載數(shù)據(jù))

<!DOCTYPE html>
<html lang="zh-CN">
<head>
  <meta charset="UTF-8">
  <title>原生JS無限滾動</title>
  <style>
    .container { width: 500px; margin: 0 auto; }
    .list { padding: 0; margin: 0; list-style: none; }
    .list-item { padding: 20px; border-bottom: 1px solid #eee; margin-bottom: 10px; }
    .loading { text-align: center; padding: 20px; color: #999; display: none; }
    .loading.show { display: block; }
  </style>
</head>
<body>
  <div class="container">
    <ul class="list" id="list"></ul>
    <div class="loading" id="loading">加載中...</div>
  </div>

  <script>
    const list = document.getElementById('list');
    const loading = document.getElementById('loading');
    let page = 1;
    const pageSize = 10;
    let isLoading = false; // 防止重復加載

    // 模擬請求數(shù)據(jù)
    function fetchData(page) {
      return new Promise((resolve) => {
        setTimeout(() => {
          const data = Array.from({ length: pageSize }, (_, i) => ({
            id: (page - 1) * pageSize + i + 1,
            content: `列表項 ${(page - 1) * pageSize + i + 1}`
          }));
          resolve(data);
        }, 1000);
      });
    }

    // 渲染列表
    function renderList(data) {
      data.forEach(item => {
        const li = document.createElement('li');
        li.className = 'list-item';
        li.textContent = item.content;
        list.appendChild(li);
      });
    }

    // 加載數(shù)據(jù)
    async function loadMore() {
      if (isLoading) return;
      isLoading = true;
      loading.classList.add('show');
      
      try {
        const data = await fetchData(page);
        renderList(data);
        page++;
      } catch (err) {
        alert('加載失敗,請重試');
      } finally {
        isLoading = false;
        loading.classList.remove('show');
      }
    }

    // 監(jiān)聽滾動事件
    window.addEventListener('scroll', () => {
      // 滾動距離 + 視口高度 >= 文檔總高度 - 100(提前加載)
      const scrollTop = document.documentElement.scrollTop || document.body.scrollTop;
      const clientHeight = document.documentElement.clientHeight;
      const scrollHeight = document.documentElement.scrollHeight;
      
      if (scrollTop + clientHeight >= scrollHeight - 100 && !isLoading) {
        loadMore();
      }
    });

    // 初始化加載第一頁
    loadMore();
  </script>
</body>
</html>

二、Vue3 核心組件(組合式API)

1. 帶插槽的卡片組件(Card.vue)

<template>
  <div class="card" :style="{ width: width + 'px' }">
    <!-- 頭部插槽 -->
    <div class="card-header" v-if="$slots.header">
      <slot name="header"></slot>
    </div>
    <!-- 主體插槽 -->
    <div class="card-body">
      <slot></slot>
    </div>
    <!-- 底部插槽 -->
    <div class="card-footer" v-if="$slots.footer">
      <slot name="footer"></slot>
    </div>
  </div>
</template>

<script setup>
import { defineProps } from 'vue';

// 定義props
const props = defineProps({
  width: {
    type: Number,
    default: 300
  }
});
</script>

<style scoped>
.card {
  border: 1px solid #e5e7eb;
  border-radius: 8px;
  overflow: hidden;
  box-shadow: 0 2px 4px rgba(0,0,0,0.05);
}
.card-header {
  padding: 16px;
  border-bottom: 1px solid #e5e7eb;
  font-weight: 600;
}
.card-body {
  padding: 16px;
}
.card-footer {
  padding: 16px;
  border-top: 1px solid #e5e7eb;
  color: #666;
}
</style>

<!-- 使用示例(App.vue) -->
<template>
  <div style="padding: 50px;">
    <Card :width="400">
      <template #header>卡片標題</template>
      <div>卡片主體內容,默認插槽</div>
      <template #footer>卡片底部 - 2025/12/27</template>
    </Card>
  </div>
</template>

<script setup>
import Card from './Card.vue';
</script>

2. 模態(tài)框組件(Modal.vue)

<template>
  <!-- 遮罩層 -->
  <div class="modal-mask" v-if="visible" @click="handleMaskClick"></div>
  <!-- 彈窗主體 -->
  <div class="modal-container" v-if="visible">
    <div class="modal-content" @click.stop>
      <!-- 頭部 -->
      <div class="modal-header">
        <h3 class="modal-title">{{ title }}</h3>
        <button class="modal-close" @click="handleClose">×</button>
      </div>
      <!-- 主體 -->
      <div class="modal-body">
        <slot></slot>
      </div>
      <!-- 底部 -->
      <div class="modal-footer" v-if="$slots.footer">
        <slot name="footer"></slot>
      </div>
    </div>
  </div>
</template>

<script setup>
import { defineProps, defineEmits } from 'vue';

const props = defineProps({
  visible: {
    type: Boolean,
    default: false
  },
  title: {
    type: String,
    default: '提示'
  },
  closeOnMask: {
    type: Boolean,
    default: true
  }
});

const emit = defineEmits(['close']);

// 關閉彈窗
const handleClose = () => {
  emit('close');
};

// 點擊遮罩關閉
const handleMaskClick = () => {
  if (props.closeOnMask) {
    handleClose();
  }
};
</script>

<style scoped>
.modal-mask {
  position: fixed;
  top: 0;
  left: 0;
  right: 0;
  bottom: 0;
  background: rgba(0,0,0,0.5);
  z-index: 1000;
}
.modal-container {
  position: fixed;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
  z-index: 1001;
}
.modal-content {
  width: 400px;
  background: white;
  border-radius: 8px;
  box-shadow: 0 4px 12px rgba(0,0,0,0.15);
}
.modal-header {
  padding: 16px;
  border-bottom: 1px solid #e5e7eb;
  display: flex;
  justify-content: space-between;
  align-items: center;
}
.modal-title {
  margin: 0;
  font-size: 16px;
}
.modal-close {
  background: transparent;
  border: none;
  font-size: 20px;
  cursor: pointer;
  color: #999;
}
.modal-close:hover {
  color: #333;
}
.modal-body {
  padding: 16px;
}
.modal-footer {
  padding: 16px;
  border-top: 1px solid #e5e7eb;
  display: flex;
  justify-content: flex-end;
  gap: 10px;
}
</style>

<!-- 使用示例(App.vue) -->
<template>
  <div style="padding: 50px;">
    <button @click="modalVisible = true">打開彈窗</button>
    <Modal 
      :visible="modalVisible" 
      title="自定義標題"
      @close="modalVisible = false"
    >
      <div>彈窗內容</div>
      <template #footer>
        <button @click="modalVisible = false">取消</button>
        <button @click="handleConfirm">確認</button>
      </template>
    </Modal>
  </div>
</template>

<script setup>
import { ref } from 'vue';
import Modal from './Modal.vue';

const modalVisible = ref(false);

const handleConfirm = () => {
  alert('確認操作');
  modalVisible.value = false;
};
</script>

3. 動態(tài)表單(可新增/刪除表單項)

<template>
  <div class="dynamic-form" style="padding: 50px;">
    <div v-for="(item, index) in formList" :key="index" class="form-item">
      <input 
        v-model="item.value" 
        placeholder="請輸入內容"
        style="padding: 8px; margin-right: 10px;"
      >
      <button @click="removeItem(index)" style="color: red;">刪除</button>
    </div>
    <button @click="addItem" style="margin-top: 10px;">新增表單項</button>
    <button @click="submitForm" style="margin-left: 10px;">提交</button>
  </div>
</template>

<script setup>
import { ref } from 'vue';

// 初始化表單列表
const formList = ref([{ value: '' }]);

// 新增表單項
const addItem = () => {
  formList.value.push({ value: '' });
};

// 刪除表單項
const removeItem = (index) => {
  if (formList.value.length <= 1) {
    alert('至少保留一個表單項');
    return;
  }
  formList.value.splice(index, 1);
};

// 提交表單
const submitForm = () => {
  const values = formList.value.map(item => item.value.trim()).filter(Boolean);
  if (values.length === 0) {
    alert('請?zhí)顚懼辽僖粋€表單項');
    return;
  }
  console.log('表單數(shù)據(jù):', formList.value);
  alert(`提交成功:${JSON.stringify(formList.value)}`);
};
</script>

4. 父子組件通信(props + emit)

<!-- 子組件 Child.vue -->
<template>
  <div class="child" style="padding: 20px; border: 1px solid #ccc; margin-top: 20px;">
    <h3>子組件</h3>
    <p>父組件傳遞的值:{{ parentMsg }}</p>
    <button @click="handleSend">向父組件傳值</button>
  </div>
</template>

<script setup>
import { defineProps, defineEmits } from 'vue';

// 接收父組件props
const props = defineProps({
  parentMsg: {
    type: String,
    default: ''
  }
});

// 定義emit事件
const emit = defineEmits(['child-send']);

// 向父組件傳值
const handleSend = () => {
  emit('child-send', '我是子組件傳遞的消息');
};
</script>

<!-- 父組件 App.vue -->
<template>
  <div style="padding: 50px;">
    <h3>父組件</h3>
    <input 
      v-model="parentMsg" 
      placeholder="請輸入要傳遞給子組件的內容"
      style="padding: 8px;"
    >
    <Child 
      :parent-msg="parentMsg"
      @child-send="handleChildMsg"
    />
    <p style="margin-top: 10px;">子組件傳遞的值:{{ childMsg }}</p>
  </div>
</template>

<script setup>
import { ref } from 'vue';
import Child from './Child.vue';

const parentMsg = ref('初始消息');
const childMsg = ref('');

// 接收子組件消息
const handleChildMsg = (msg) => {
  childMsg.value = msg;
};
</script>

5. 自定義v-model

<!-- 子組件 CustomInput.vue -->
<template>
  <input 
    :value="modelValue" 
    @input="handleInput"
    placeholder="自定義v-model"
    style="padding: 8px;"
  >
</template>

<script setup>
import { defineProps, defineEmits } from 'vue';

// 接收v-model的值(默認是modelValue)
const props = defineProps({
  modelValue: {
    type: String,
    default: ''
  }
});

// 觸發(fā)更新事件(默認是update:modelValue)
const emit = defineEmits(['update:modelValue']);

const handleInput = (e) => {
  emit('update:modelValue', e.target.value);
};
</script>

<!-- 使用示例(App.vue) -->
<template>
  <div style="padding: 50px;">
    <CustomInput v-model="inputValue" />
    <p>輸入的值:{{ inputValue }}</p>
  </div>
</template>

<script setup>
import { ref } from 'vue';
import CustomInput from './CustomInput.vue';

const inputValue = ref('');
</script>

6. 倒計時組件

<template>
  <div class="countdown" style="padding: 50px; font-size: 20px;">
    <button 
      @click="startCountdown" 
      :disabled="counting"
      style="padding: 8px 16px; margin-right: 10px;"
    >
      {{ counting ? `${count}秒后重新獲取` : '開始倒計時' }}
    </button>
  </div>
</template>

<script setup>
import { ref, onUnmounted } from 'vue';

const count = ref(60);
const counting = ref(false);
let timer = null;

// 開始倒計時
const startCountdown = () => {
  if (counting.value) return;
  counting.value = true;
  timer = setInterval(() => {
    count.value--;
    if (count.value <= 0) {
      clearInterval(timer);
      count.value = 60;
      counting.value = false;
    }
  }, 1000);
};

// 組件卸載時清除定時器
onUnmounted(() => {
  if (timer) clearInterval(timer);
});
</script>

7. 購物車組件

<template>
  <div class="cart" style="width: 600px; margin: 50px auto;">
    <h3>購物車</h3>
    <table border="1" width="100%" cellpadding="10" cellspacing="0">
      <thead>
        <tr>
          <th>商品名稱</th>
          <th>單價</th>
          <th>數(shù)量</th>
          <th>小計</th>
          <th>操作</th>
        </tr>
      </thead>
      <tbody>
        <tr v-for="(item, index) in cartList" :key="item.id">
          <td>{{ item.name }}</td>
          <td>¥{{ item.price }}</td>
          <td>
            <button @click="changeCount(item, -1)" :disabled="item.count <= 1">-</button>
            <span style="margin: 0 10px;">{{ item.count }}</span>
            <button @click="changeCount(item, 1)">+</button>
          </td>
          <td>¥{{ (item.price * item.count).toFixed(2) }}</td>
          <td>
            <button @click="removeItem(index)" style="color: red;">刪除</button>
          </td>
        </tr>
      </tbody>
    </table>
    <div style="margin-top: 20px; text-align: right;">
      總價:¥{{ totalPrice.toFixed(2) }}
    </div>
  </div>
</template>

<script setup>
import { ref, computed } from 'vue';

// 初始化購物車數(shù)據(jù)
const cartList = ref([
  { id: 1, name: '商品1', price: 99.9, count: 1 },
  { id: 2, name: '商品2', price: 199.9, count: 1 },
  { id: 3, name: '商品3', price: 299.9, count: 1 }
]);

// 改變數(shù)量
const changeCount = (item, num) => {
  item.count += num;
  if (item.count < 1) item.count = 1;
};

// 刪除商品
const removeItem = (index) => {
  cartList.value.splice(index, 1);
};

// 計算總價
const totalPrice = computed(() => {
  return cartList.value.reduce((sum, item) => {
    return sum + item.price * item.count;
  }, 0);
});
</script>

8. TodoList組件

<template>
  <div class="todo-list" style="width: 400px; margin: 50px auto;">
    <h3>TodoList</h3>
    <!-- 新增 -->
    <div style="display: flex; gap: 10px; margin-bottom: 20px;">
      <input 
        v-model="inputValue" 
        placeholder="請輸入待辦事項"
        style="flex: 1; padding: 8px;"
        @keyup.enter="addTodo"
      >
      <button @click="addTodo">添加</button>
    </div>
    <!-- 列表 -->
    <div class="todo-items">
      <div 
        v-for="(todo, index) in todoList" 
        :key="todo.id"
        style="display: flex; align-items: center; padding: 8px; border-bottom: 1px solid #eee;"
      >
        <input 
          type="checkbox" 
          v-model="todo.done"
          style="margin-right: 10px;"
        >
        <span :style="{ textDecoration: todo.done ? 'line-through' : 'none', color: todo.done ? '#999' : '#333' }">
          {{ todo.content }}
        </span>
        <button 
          @click="deleteTodo(index)"
          style="margin-left: auto; color: red; border: none; background: transparent; cursor: pointer;"
        >
          刪除
        </button>
      </div>
    </div>
    <!-- 統(tǒng)計 -->
    <div style="margin-top: 20px; display: flex; justify-content: space-between;">
      <span>已完成:{{ doneCount }} / 總數(shù):{{ todoList.length }}</span>
      <button @click="clearDone" style="color: #666;">清空已完成</button>
    </div>
  </div>
</template>

<script setup>
import { ref, computed } from 'vue';

const inputValue = ref('');
const todoList = ref([
  { id: 1, content: '學習Vue', done: false },
  { id: 2, content: '手撕組件', done: true }
]);

// 添加待辦
const addTodo = () => {
  const content = inputValue.value.trim();
  if (!content) return;
  todoList.value.push({
    id: Date.now(),
    content,
    done: false
  });
  inputValue.value = '';
};

// 刪除待辦
const deleteTodo = (index) => {
  todoList.value.splice(index, 1);
};

// 已完成數(shù)量
const doneCount = computed(() => {
  return todoList.value.filter(todo => todo.done).length;
});

// 清空已完成
const clearDone = () => {
  todoList.value = todoList.value.filter(todo => !todo.done);
};
</script>

三、React 核心組件(Hooks)

1. 帶插槽的卡片組件(Card.jsx)

import React from 'react';
import './Card.css';

const Card = ({ width = 300, children, header, footer }) => {
  return (
    <div className="card" style={{ width: `${width}px` }}>
      {header && <div className="card-header">{header}</div>}
      <div className="card-body">{children}</div>
      {footer && <div className="card-footer">{footer}</div>}
    </div>
  );
};

export default Card;

// Card.css
.card {
  border: 1px solid #e5e7eb;
  border-radius: 8px;
  overflow: hidden;
  box-shadow: 0 2px 4px rgba(0,0,0,0.05);
}
.card-header {
  padding: 16px;
  border-bottom: 1px solid #e5e7eb;
  font-weight: 600;
}
.card-body {
  padding: 16px;
}
.card-footer {
  padding: 16px;
  border-top: 1px solid #e5e7eb;
  color: #666;
}

// 使用示例(App.jsx)
import React from 'react';
import Card from './Card';

function App() {
  return (
    <div style={{ padding: '50px' }}>
      <Card width={400} header="卡片標題" footer="卡片底部 - 2025/12/27">
        <div>卡片主體內容</div>
      </Card>
    </div>
  );
}

export default App;

2. 模態(tài)框組件(Modal.jsx)

import React from 'react';
import './Modal.css';

const Modal = ({ visible, title = '提示', closeOnMask = true, onClose, children, footer }) => {
  if (!visible) return null;

  // 點擊遮罩關閉
  const handleMaskClick = () => {
    if (closeOnMask) {
      onClose?.();
    }
  };

  return (
    <>
      <div className="modal-mask" onClick={handleMaskClick}></div>
      <div className="modal-container">
        <div className="modal-content" onClick={(e) => e.stopPropagation()}>
          <div className="modal-header">
            <h3 className="modal-title">{title}</h3>
            <button className="modal-close" onClick={onClose}>×</button>
          </div>
          <div className="modal-body">{children}</div>
          {footer && <div className="modal-footer">{footer}</div>}
        </div>
      </div>
    </>
  );
};

export default Modal;

// Modal.css
.modal-mask {
  position: fixed;
  top: 0;
  left: 0;
  right: 0;
  bottom: 0;
  background: rgba(0,0,0,0.5);
  z-index: 1000;
}
.modal-container {
  position: fixed;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
  z-index: 1001;
}
.modal-content {
  width: 400px;
  background: white;
  border-radius: 8px;
  box-shadow: 0 4px 12px rgba(0,0,0,0.15);
}
.modal-header {
  padding: 16px;
  border-bottom: 1px solid #e5e7eb;
  display: flex;
  justify-content: space-between;
  align-items: center;
}
.modal-title {
  margin: 0;
  font-size: 16px;
}
.modal-close {
  background: transparent;
  border: none;
  font-size: 20px;
  cursor: pointer;
  color: #999;
}
.modal-close:hover {
  color: #333;
}
.modal-body {
  padding: 16px;
}
.modal-footer {
  padding: 16px;
  border-top: 1px solid #e5e7eb;
  display: flex;
  justify-content: flex-end;
  gap: 10px;
}

// 使用示例(App.jsx)
import React, { useState } from 'react';
import Modal from './Modal';

function App() {
  const [modalVisible, setModalVisible] = useState(false);

  const handleConfirm = () => {
    alert('確認操作');
    setModalVisible(false);
  };

  return (
    <div style={{ padding: '50px' }}>
      <button onClick={() => setModalVisible(true)}>打開彈窗</button>
      <Modal
        visible={modalVisible}
        title="自定義標題"
        onClose={() => setModalVisible(false)}
        footer={
          <>
            <button onClick={() => setModalVisible(false)}>取消</button>
            <button onClick={handleConfirm}>確認</button>
          </>
        }
      >
        <div>彈窗內容</div>
      </Modal>
    </div>
  );
}

export default App;

3. 動態(tài)表單(可新增/刪除表單項)

import React, { useState } from 'react';

function DynamicForm() {
  const [formList, setFormList] = useState([{ value: '' }]);

  // 新增表單項
  const addItem = () => {
    setFormList([...formList, { value: '' }]);
  };

  // 刪除表單項
  const removeItem = (index) => {
    if (formList.length <= 1) {
      alert('至少保留一個表單項');
      return;
    }
    const newList = [...formList];
    newList.splice(index, 1);
    setFormList(newList);
  };

  // 輸入變更
  const handleInputChange = (index, value) => {
    const newList = [...formList];
    newList[index].value = value;
    setFormList(newList);
  };

  // 提交表單
  const submitForm = () => {
    const values = formList.map(item => item.value.trim()).filter(Boolean);
    if (values.length === 0) {
      alert('請?zhí)顚懼辽僖粋€表單項');
      return;
    }
    console.log('表單數(shù)據(jù):', formList);
    alert(`提交成功:${JSON.stringify(formList)}`);
  };

  return (
    <div style={{ padding: '50px' }}>
      {formList.map((item, index) => (
        <div key={index} style={{ marginBottom: '10px' }}>
          <input
            value={item.value}
            onChange={(e) => handleInputChange(index, e.target.value)}
            placeholder="請輸入內容"
            style={{ padding: '8px', marginRight: '10px' }}
          />
          <button onClick={() => removeItem(index)} style={{ color: 'red' }}>刪除</button>
        </div>
      ))}
      <button onClick={addItem} style={{ marginRight: '10px' }}>新增表單項</button>
      <button onClick={submitForm}>提交</button>
    </div>
  );
}

export default DynamicForm;

4. 父子組件通信(props + 回調)

// 子組件 Child.jsx
import React from 'react';

const Child = ({ parentMsg, onChildSend }) => {
  const handleSend = () => {
    onChildSend('我是子組件傳遞的消息');
  };

  return (
    <div style={{ padding: '20px', border: '1px solid #ccc', marginTop: '20px' }}>
      <h3>子組件</h3>
      <p>父組件傳遞的值:{parentMsg}</p>
      <button onClick={handleSend}>向父組件傳值</button>
    </div>
  );
};

export default Child;

// 父組件 App.jsx
import React, { useState } from 'react';
import Child from './Child';

function App() {
  const [parentMsg, setParentMsg] = useState('初始消息');
  const [childMsg, setChildMsg] = useState('');

  const handleChildMsg = (msg) => {
    setChildMsg(msg);
  };

  return (
    <div style={{ padding: '50px' }}>
      <h3>父組件</h3>
      <input
        value={parentMsg}
        onChange={(e) => setParentMsg(e.target.value)}
        placeholder="請輸入要傳遞給子組件的內容"
        style={{ padding: '8px' }}
      />
      <Child parentMsg={parentMsg} onChildSend={handleChildMsg} />
      <p style={{ marginTop: '10px' }}>子組件傳遞的值:{childMsg}</p>
    </div>
  );
}

export default App;

5. 雙向綁定(useState + onChange)

import React, { useState } from 'react';

function CustomInput() {
  const [inputValue, setInputValue] = useState('');

  return (
    <div style={{ padding: '50px' }}>
      <input
        value={inputValue}
        onChange={(e) => setInputValue(e.target.value)}
        placeholder="雙向綁定示例"
        style={{ padding: '8px' }}
      />
      <p>輸入的值:{inputValue}</p>
    </div>
  );
}

export default CustomInput;

6. 倒計時組件

import React, { useState, useEffect } from 'react';

function Countdown() {
  const [count, setCount] = useState(60);
  const [counting, setCounting] = useState(false);

  // 清除定時器
  useEffect(() => {
    let timer = null;
    if (counting) {
      timer = setInterval(() => {
        setCount(prev => {
          if (prev <= 1) {
            setCounting(false);
            return 60;
          }
          return prev - 1;
        });
      }, 1000);
    }
    return () => clearInterval(timer);
  }, [counting]);

  const startCountdown = () => {
    if (!counting) {
      setCounting(true);
    }
  };

  return (
    <div style={{ padding: '50px', fontSize: '20px' }}>
      <button
        onClick={startCountdown}
        disabled={counting}
        style={{ padding: '8px 16px', marginRight: '10px' }}
      >
        {counting ? `${count}秒后重新獲取` : '開始倒計時'}
      </button>
    </div>
  );
}

export default Countdown;

7. 購物車組件

import React, { useState, useMemo } from 'react';

function Cart() {
  const [cartList, setCartList] = useState([
    { id: 1, name: '商品1', price: 99.9, count: 1 },
    { id: 2, name: '商品2', price: 199.9, count: 1 },
    { id: 3, name: '商品3', price: 299.9, count: 1 }
  ]);

  // 改變數(shù)量
  const changeCount = (id, num) => {
    setCartList(
      cartList.map(item => {
        if (item.id === id) {
          const newCount = item.count + num;
          return { ...item, count: newCount < 1 ? 1 : newCount };
        }
        return item;
      })
    );
  };

  // 刪除商品
  const removeItem = (id) => {
    setCartList(cartList.filter(item => item.id !== id));
  };

  // 計算總價
  const totalPrice = useMemo(() => {
    return cartList.reduce((sum, item) => sum + item.price * item.count, 0);
  }, [cartList]);

  return (
    <div style={{ width: '600px', margin: '50px auto' }}>
      <h3>購物車</h3>
      <table border="1" width="100%" cellpadding="10" cellspacing="0">
        <thead>
          <tr>
            <th>商品名稱</th>
            <th>單價</th>
            <th>數(shù)量</th>
            <th>小計</th>
            <th>操作</th>
          </tr>
        </thead>
        <tbody>
          {cartList.map(item => (
            <tr key={item.id}>
              <td>{item.name}</td>
              <td>¥{item.price}</td>
              <td>
                <button onClick={() => changeCount(item.id, -1)} disabled={item.count <= 1}>-</button>
                <span style={{ margin: '0 10px' }}>{item.count}</span>
                <button onClick={() => changeCount(item.id, 1)}>+</button>
              </td>
              <td>¥{(item.price * item.count).toFixed(2)}</td>
              <td>
                <button onClick={() => removeItem(item.id)} style={{ color: 'red' }}>刪除</button>
              </td>
            </tr>
          ))}
        </tbody>
      </table>
      <div style={{ marginTop: '20px', textAlign: 'right' }}>
        總價:¥{totalPrice.toFixed(2)}
      </div>
    </div>
  );
}

export default Cart;

8. TodoList組件

import React, { useState, useMemo } from 'react';

function TodoList() {
  const [inputValue, setInputValue] = useState('');
  const [todoList, setTodoList] = useState([
    { id: 1, content: '學習React', done: false },
    { id: 2, content: '手撕組件', done: true }
  ]);

  // 添加待辦
  const addTodo = () => {
    const content = inputValue.trim();
    if (!content) return;
    setTodoList([
      ...todoList,
      { id: Date.now(), content, done: false }
    ]);
    setInputValue('');
  };

  // 刪除待辦
  const deleteTodo = (id) => {
    setTodoList(todoList.filter(item => item.id !== id));
  };

  // 切換完成狀態(tài)
  const toggleDone = (id) => {
    setTodoList(
      todoList.map(item => 
        item.id === id ? { ...item, done: !item.done } : item
      )
    );
  };

  // 清空已完成
  const clearDone = () => {
    setTodoList(todoList.filter(item => !item.done));
  };

  // 已完成數(shù)量
  const doneCount = useMemo(() => {
    return todoList.filter(item => item.done).length;
  }, [todoList]);

  return (
    <div style={{ width: '400px', margin: '50px auto' }}>
      <h3>TodoList</h3>
      {/* 新增 */}
      <div style={{ display: 'flex', gap: '10px', marginBottom: '20px' }}>
        <input
          value={inputValue}
          onChange={(e) => setInputValue(e.target.value)}
          placeholder="請輸入待辦事項"
          style={{ flex: 1, padding: '8px' }}
          onKeyDown={(e) => e.key === 'Enter' && addTodo()}
        />
        <button onClick={addTodo}>添加</button>
      </div>
      {/* 列表 */}
      <div>
        {todoList.map(item => (
          <div
            key={item.id}
            style={{ display: 'flex', alignItems: 'center', padding: '8px', borderBottom: '1px solid #eee' }}
          >
            <input
              type="checkbox"
              checked={item.done}
              onChange={() => toggleDone(item.id)}
              style={{ marginRight: '10px' }}
            />
            <span
              style={{
                textDecoration: item.done ? 'line-through' : 'none',
                color: item.done ? '#999' : '#333'
              }}
            >
              {item.content}
            </span>
            <button
              onClick={() => deleteTodo(item.id)}
              style={{ marginLeft: 'auto', color: 'red', border: 'none', background: 'transparent', cursor: 'pointer' }}
            >
              刪除
            </button>
          </div>
        ))}
      </div>
      {/* 統(tǒng)計 */}
      <div style={{ marginTop: '20px', display: 'flex', justifyContent: 'space-between' }}>
        <span>已完成:{doneCount} / 總數(shù):{todoList.length}</span>
        <button onClick={clearDone} style={{ color: '#666' }}>清空已完成</button>
      </div>
    </div>
  );
}

export default TodoList;

核心說明

  1. 所有代碼均保證可直接運行,無第三方依賴(僅Vue/React核心庫)
  2. 原生JS部分:聚焦核心邏輯,樣式精簡但完整,適配常見場景
  3. Vue部分:基于Vue3組合式API(setup語法),符合最新開發(fā)規(guī)范
  4. React部分:基于Hooks(useState/useEffect/useMemo),函數(shù)式組件寫法
  5. 每個組件都包含核心功能+使用示例,可直接手撕到面試場景中

面試手撕時,可根據(jù)時間精簡樣式,重點實現(xiàn)核心邏輯(如輪播圖的切換、表單驗證的規(guī)則、雙向綁定的核心原理等)。

總結

到此這篇關于前端面試手撕合集之純HTML、Vue和React組件的文章就介紹到這了,更多相關前端純HTML、Vue和React組件內容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!

相關文章

  • vue自定義一個v-model的實現(xiàn)代碼

    vue自定義一個v-model的實現(xiàn)代碼

    這篇文章主要介紹了vue自定義一個v-model的實現(xiàn)代碼,非常不錯,具有一定的參考借鑒價值,需要的朋友可以參考下
    2018-06-06
  • antd?vue?table表格內容如何格式化

    antd?vue?table表格內容如何格式化

    這篇文章主要介紹了antd?vue?table表格內容如何格式化,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教
    2022-01-01
  • vue-cli的index.html中使用環(huán)境變量方式

    vue-cli的index.html中使用環(huán)境變量方式

    這篇文章主要介紹了vue-cli的index.html中使用環(huán)境變量方式,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教
    2022-10-10
  • 教你利用Vue3模仿Windows窗口

    教你利用Vue3模仿Windows窗口

    最近學習了Vue3,利用vue3做了個好玩的項目,所以下面這篇文章主要給大家介紹了關于如何利用Vue3模仿Windows窗口的相關資料,文中通過實例代碼介紹的非常詳細,需要的朋友可以參考下
    2022-04-04
  • Vue3報錯‘defineProps‘?is?not?defined的解決方法

    Vue3報錯‘defineProps‘?is?not?defined的解決方法

    最近工作中遇到vue3中使用defineProps中報錯,飄紅,所以這篇文章主要給大家介紹了關于Vue3報錯‘defineProps‘?is?not?defined的解決方法,需要的朋友可以參考下
    2023-01-01
  • vue中實現(xiàn)拖拽排序功能的詳細教程

    vue中實現(xiàn)拖拽排序功能的詳細教程

    在業(yè)務中列表拖拽排序是比較常見的需求,下面這篇文章主要給大家介紹了關于vue中實現(xiàn)拖拽排序功能的詳細教程,文中通過實例代碼介紹的非常詳細,需要的朋友可以參考下
    2022-08-08
  • vue在.js文件中如何進行路由跳轉

    vue在.js文件中如何進行路由跳轉

    這篇文章主要介紹了vue在.js文件中如何進行路由跳轉,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教
    2022-12-12
  • 詳解Vue.js之視圖和數(shù)據(jù)的雙向綁定(v-model)

    詳解Vue.js之視圖和數(shù)據(jù)的雙向綁定(v-model)

    本篇文章主要介紹了Vue.js之視圖和數(shù)據(jù)的雙向綁定(v-model),使用v-model指令,使得視圖和數(shù)據(jù)實現(xiàn)雙向綁定,有興趣的可以了解一下
    2017-06-06
  • Vue項目打包、合并及壓縮優(yōu)化網(wǎng)頁響應速度

    Vue項目打包、合并及壓縮優(yōu)化網(wǎng)頁響應速度

    網(wǎng)站頁面的響應速度與用戶體驗息息相關,直接影響到用戶是否愿意繼續(xù)訪問你的網(wǎng)站,所以這篇文章主要給大家介紹了關于Vue項目打包、合并及壓縮優(yōu)化網(wǎng)頁響應速度的相關資料,需要的朋友可以參考下
    2021-07-07
  • vue element自定義表單驗證請求后端接口驗證

    vue element自定義表單驗證請求后端接口驗證

    這篇文章主要介紹了vue element自定義表單驗證請求后端接口驗證,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧
    2019-12-12

最新評論

侯马市| 榆社县| 常熟市| 泗阳县| 元氏县| 扶沟县| 香格里拉县| 大洼县| 垫江县| 张北县| 朝阳市| 靖安县| 西林县| 南川市| 和田县| 彰化市| 凤山市| 搜索| 万宁市| 沧州市| 永和县| 博爱县| 北碚区| 益阳市| 福鼎市| 石狮市| 安阳市| 德兴市| 辽阳县| 唐山市| 察雅县| 阳谷县| 望都县| 松桃| 措勤县| 沙洋县| 新民市| 富民县| 金阳县| 即墨市| 和静县|